| Index: third_party/WebKit/Source/devtools/front_end/cm/codemirror.js
|
| diff --git a/third_party/WebKit/Source/devtools/front_end/cm/codemirror.js b/third_party/WebKit/Source/devtools/front_end/cm/codemirror.js
|
| index 03a34dbbfb78b10557a43bfbb1504d56f36093b2..cd73b84a1253b395847152bbcf58d825dad519e1 100644
|
| --- a/third_party/WebKit/Source/devtools/front_end/cm/codemirror.js
|
| +++ b/third_party/WebKit/Source/devtools/front_end/cm/codemirror.js
|
| @@ -13,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";
|
|
|
| @@ -21,29 +21,30 @@
|
|
|
| // 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_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(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 ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
|
| - 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 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_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
|
| @@ -67,33 +68,40 @@
|
| 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
|
| - keySeq: null // Unfinished key sequence
|
| + 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 && ie_version < 11) setTimeout(bind(resetInput, this, true), 20);
|
| + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20);
|
|
|
| registerEventHandlers(this);
|
| ensureGlobalHandlers();
|
| @@ -102,7 +110,7 @@
|
| this.curOp.forceUpdate = true;
|
| attachDoc(this, doc);
|
|
|
| - if ((options.autofocus && !mobile) || activeElt() == display.input)
|
| + if ((options.autofocus && !mobile) || cm.hasFocus())
|
| setTimeout(bind(onFocus, this), 20);
|
| else
|
| onBlur(this);
|
| @@ -110,6 +118,7 @@
|
| 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
|
| @@ -125,31 +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;");
|
| // Covers bottom-right square when both scrollbars are present.
|
| d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
|
| - d.scrollbarFiller.setAttribute("not-content", "true");
|
| + 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("not-content", "true");
|
| + 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.
|
| @@ -178,15 +173,11 @@
|
| 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.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 && ie_version < 8) { 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"; }
|
| + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true;
|
|
|
| if (place) {
|
| if (place.appendChild) place.appendChild(d.wrapper);
|
| @@ -213,25 +204,13 @@
|
| // 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;
|
| @@ -247,6 +226,10 @@
|
| // Used to track whether anything happened since the context menu
|
| // was opened.
|
| d.selForContextMenu = null;
|
| +
|
| + d.activeTouch = null;
|
| +
|
| + input.init(d);
|
| }
|
|
|
| // STATE UPDATES
|
| @@ -428,7 +411,7 @@
|
| if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
|
| });
|
|
|
| - this.checkedOverlay = false;
|
| + 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";
|
| }
|
| @@ -463,29 +446,43 @@
|
| this.horiz.firstChild.style.width = "0";
|
| }
|
|
|
| - if (!this.checkedOverlay && measure.clientHeight > 0) {
|
| - if (sWidth == 0) this.overlayHack();
|
| - this.checkedOverlay = true;
|
| + 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);
|
| },
|
| - overlayHack: function() {
|
| + zeroWidthHack: function() {
|
| var w = mac && !mac_geMountainLion ? "12px" : "18px";
|
| - this.horiz.style.minHeight = this.vert.style.minWidth = w;
|
| - var self = this;
|
| - var barMouseDown = function(e) {
|
| - if (e_target(e) != self.vert && e_target(e) != self.horiz)
|
| - operation(self.cm, onMouseDown)(e);
|
| - };
|
| - on(this.vert, "mousedown", barMouseDown);
|
| - on(this.horiz, "mousedown", barMouseDown);
|
| + 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;
|
| @@ -514,10 +511,11 @@
|
|
|
| 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(bind(focusInput, cm), 0);
|
| + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0);
|
| });
|
| - node.setAttribute("not-content", "true");
|
| + node.setAttribute("cm-not-content", "true");
|
| }, function(pos, axis) {
|
| if (axis == "horizontal") setScrollLeft(cm, pos);
|
| else setScrollTop(cm, pos);
|
| @@ -546,6 +544,7 @@
|
|
|
| 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";
|
| @@ -614,7 +613,7 @@
|
| "CodeMirror-linenumber CodeMirror-gutter-elt"));
|
| var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
|
| display.lineGutter.style.width = "";
|
| - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
|
| + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;
|
| display.lineNumWidth = display.lineNumInnerWidth + padding;
|
| display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
|
| display.lineGutter.style.width = display.lineNumWidth + "px";
|
| @@ -649,8 +648,18 @@
|
| 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]);
|
| + };
|
| +
|
| function maybeClipScrollbars(cm) {
|
| var display = cm.display;
|
| if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
|
| @@ -724,7 +733,7 @@
|
| // width and height.
|
| removeChildren(display.cursorDiv);
|
| removeChildren(display.selectionDiv);
|
| - display.gutters.style.height = 0;
|
| + display.gutters.style.height = display.sizer.style.minHeight = 0;
|
|
|
| if (different) {
|
| display.lastWrapHeight = update.wrapperHeight;
|
| @@ -738,12 +747,10 @@
|
| }
|
|
|
| function postUpdateDisplay(cm, update) {
|
| - var force = update.force, viewport = update.viewport;
|
| + var viewport = update.viewport;
|
| +
|
| for (var first = true;; first = false) {
|
| - if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) {
|
| - force = true;
|
| - } else {
|
| - force = 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)};
|
| @@ -757,13 +764,13 @@
|
| updateHeightsInViewport(cm);
|
| var barMeasure = measureForScrollbars(cm);
|
| updateSelection(cm);
|
| - setDocumentHeight(cm, barMeasure);
|
| updateScrollbars(cm, barMeasure);
|
| + setDocumentHeight(cm, barMeasure);
|
| }
|
|
|
| - signalLater(cm, "update", cm);
|
| + update.signal(cm, "update", cm);
|
| if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
|
| - signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
|
| + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
|
| cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;
|
| }
|
| }
|
| @@ -775,16 +782,16 @@
|
| postUpdateDisplay(cm, update);
|
| var barMeasure = measureForScrollbars(cm);
|
| updateSelection(cm);
|
| - setDocumentHeight(cm, barMeasure);
|
| updateScrollbars(cm, barMeasure);
|
| + setDocumentHeight(cm, barMeasure);
|
| + update.finish();
|
| }
|
| }
|
|
|
| function setDocumentHeight(cm, measure) {
|
| cm.display.sizer.style.minHeight = measure.docHeight + "px";
|
| - var total = measure.docHeight + cm.display.barHeight;
|
| - cm.display.heightForcer.style.top = total + "px";
|
| - cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "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
|
| @@ -818,7 +825,7 @@
|
| // 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
|
| @@ -861,7 +868,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
|
| @@ -892,7 +899,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;
|
| }
|
| @@ -967,14 +974,24 @@
|
| 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", "left: " +
|
| - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
|
| - "px; width: " + 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"]))
|
| @@ -992,14 +1009,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
|
| @@ -1011,25 +1028,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.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
|
| @@ -1072,6 +1090,924 @@
|
| 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/plain");
|
| + 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) {
|
| + field.setAttribute("autocorrect", "off");
|
| + field.setAttribute("autocapitalize", "off");
|
| + field.setAttribute("spellcheck", "false");
|
| + }
|
| +
|
| + // 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);
|
| +
|
| + on(div, "paste", function(e) {
|
| + if (!signalDOMEvent(cm, e)) handlePaste(e, cm);
|
| + })
|
| +
|
| + 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");
|
| + });
|
| + }
|
| + }
|
| + // iOS exposes the clipboard API, but seems to discard content inserted into it
|
| + if (e.clipboardData && !ios) {
|
| + e.preventDefault();
|
| + e.clipboardData.clearData();
|
| + e.clipboardData.setData("text/plain", lastCopied.text.join("\n"));
|
| + } else {
|
| + // 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();
|
| + }, 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 += after.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;
|
| + }
|
| + }
|
| + for (;;) {
|
| + walk(from);
|
| + if (from == to) break;
|
| + from = from.nextSibling;
|
| + }
|
| + return text;
|
| + }
|
| +
|
| + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
|
| +
|
| // SELECTION / CURSOR
|
|
|
| // Selection objects are immutable. A new one is created every time
|
| @@ -1228,7 +2164,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) {
|
| @@ -1236,7 +2172,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);
|
| @@ -1262,7 +2199,7 @@
|
|
|
| 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 = options && options.bias ||
|
| (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
|
| @@ -1296,8 +2233,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);
|
| @@ -1306,103 +2244,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 drawSelection(cm) {
|
| - var display = cm.display, doc = cm.doc, result = {};
|
| + function updateSelection(cm) {
|
| + 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)
|
| - drawSelectionCursor(cm, range, curFragment);
|
| + drawSelectionCursor(cm, range.head, curFragment);
|
| if (!collapsed)
|
| 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();
|
| - 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;
|
| }
|
|
|
| - function showSelection(cm, drawn) {
|
| - removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
|
| - removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
|
| - if (drawn.teTop != null) {
|
| - cm.display.inputDiv.style.top = drawn.teTop + "px";
|
| - cm.display.inputDiv.style.left = drawn.teLeft + "px";
|
| - }
|
| - }
|
| -
|
| - function updateSelection(cm) {
|
| - showSelection(cm, drawSelection(cm));
|
| - }
|
| -
|
| // Draws a cursor for the given range
|
| - function drawSelectionCursor(cm, range, output) {
|
| - var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
|
| + 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";
|
| @@ -1526,8 +2452,8 @@
|
|
|
| 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;
|
| - var highlighted = highlightLine(cm, line, state, true);
|
| + 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;
|
| @@ -1536,9 +2462,10 @@
|
| 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) changedLines.push(doc.frontier);
|
| - line.stateAfter = copyState(doc.mode, state);
|
| + 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;
|
| @@ -1683,10 +2610,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);
|
|
|
| @@ -1722,9 +2651,7 @@
|
|
|
| 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.
|
| @@ -1758,23 +2685,32 @@
|
| 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.
|
| 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(mStart + start))) --start;
|
| - while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
|
| - if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
|
| + 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 if (ie && cm.options.lineWrapping) {
|
| - var rects = range(node, start, end).getClientRects();
|
| - if (rects.length)
|
| - rect = rects[bias == "right" ? rects.length - 1 : 0];
|
| - else
|
| - rect = nullRect;
|
| - } else {
|
| - rect = range(node, start, end).getBoundingClientRect() || nullRect;
|
| - }
|
| + else
|
| + rect = getUsefulRect(range(node, start, end).getClientRects(), bias)
|
| if (rect.left || rect.right || start == 0) break;
|
| end = start;
|
| start = start - 1;
|
| @@ -2000,10 +2936,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;
|
| @@ -2076,6 +3025,7 @@
|
| 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 (operationGroup) {
|
| @@ -2094,12 +3044,12 @@
|
| var callbacks = group.delayedCallbacks, i = 0;
|
| do {
|
| for (; i < callbacks.length; i++)
|
| - callbacks[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++](op.cm);
|
| + op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm);
|
| }
|
| } while (i < callbacks.length);
|
| }
|
| @@ -2169,7 +3119,7 @@
|
| }
|
|
|
| if (op.updatedDisplay || op.selectionChanged)
|
| - op.newSelectionNodes = drawSelection(cm);
|
| + op.preparedSelection = display.input.prepareSelection(op.focus);
|
| }
|
|
|
| function endOperation_W2(op) {
|
| @@ -2182,17 +3132,19 @@
|
| cm.display.maxLineChanged = false;
|
| }
|
|
|
| - if (op.newSelectionNodes)
|
| - showSelection(cm, op.newSelectionNodes);
|
| - if (op.updatedDisplay)
|
| - setDocumentHeight(cm, op.barMeasure);
|
| + 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)
|
| - resetInput(cm, op.typing);
|
| + cm.display.input.reset(op.typing);
|
| + if (takeFocus) ensureFocus(op.cm);
|
| }
|
|
|
| function endOperation_finish(op) {
|
| @@ -2211,7 +3163,7 @@
|
| 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 - displayWidth(cm), op.scrollLeft));
|
| + 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);
|
| @@ -2237,6 +3189,8 @@
|
| // Fire change events, and delayed event handlers
|
| if (op.changeObjs)
|
| signal(cm, "changes", cm, op.changeObjs);
|
| + if (op.update)
|
| + op.update.finish();
|
| }
|
|
|
| // Run the given function in an operation
|
| @@ -2462,167 +3416,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);
|
| - }
|
| -
|
| - // This will be set to an array of strings when copying, so that,
|
| - // when pasting, we know what kind of selections the copied text
|
| - // was made out of.
|
| - var lastCopied = null;
|
| -
|
| - // 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) && !prevInput) || isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
|
| - return false;
|
| - // See paste handler for more on the fakedLastChar kludge
|
| - if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
|
| - input.value = input.value.substring(0, input.value.length - 1);
|
| - cm.state.fakedLastChar = 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 && cm.display.inputHasSelection === text ||
|
| - mac && /[\uf700-\uf7ff]/.test(text)) {
|
| - resetInput(cm);
|
| - return false;
|
| - }
|
| -
|
| - var withOp = !cm.curOp;
|
| - if (withOp) startOperation(cm);
|
| - cm.display.shift = false;
|
| -
|
| - if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
|
| - prevInput = "\u200b";
|
| - // 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 = null;
|
| - if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
|
| - if (lastCopied && lastCopied.join("\n") == inserted)
|
| - multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
|
| - else if (textLines.length == doc.sel.ranges.length)
|
| - multiPaste = map(textLines, function(l) { return [l]; });
|
| - }
|
| -
|
| - // 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 ? multiPaste[i % multiPaste.length] : 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 mode = cm.getModeAt(range.head);
|
| - var end = changeEnd(changeEvent);
|
| - if (mode.electricChars) {
|
| - for (var j = 0; j < mode.electricChars.length; j++)
|
| - if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
|
| - indentLine(cm, end.line, "smart");
|
| - break;
|
| - }
|
| - } else if (mode.electricInput) {
|
| - if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
|
| - indentLine(cm, end.line, "smart");
|
| - }
|
| - }
|
| - }
|
| - 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) {
|
| - if (cm.display.contextMenuPending) return;
|
| - 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_version >= 9) cm.display.inputHasSelection = content;
|
| - } else if (!typing) {
|
| - cm.display.prevInput = cm.display.input.value = "";
|
| - if (ie && ie_version >= 9) 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
|
| @@ -2641,15 +3434,64 @@
|
| }));
|
| 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() {
|
| @@ -2667,86 +3509,33 @@
|
| // Prevent wrapper from ever scrolling
|
| on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
|
|
|
| - on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); });
|
| - on(d.input, "input", function() {
|
| - if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
|
| - readInput(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() {
|
| - // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
|
| - // Add a char to the end of textarea before paste occur so that
|
| - // selection doesn't span to the end of textarea.
|
| - if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
|
| - var start = d.input.selectionStart, end = d.input.selectionEnd;
|
| - d.input.value += "$";
|
| - // The selection end needs to be set before the start, otherwise there
|
| - // can be an intermediate non-empty selection between the two, which
|
| - // can override the middle-click paste buffer on linux and cause the
|
| - // wrong thing to get pasted.
|
| - d.input.selectionEnd = end;
|
| - d.input.selectionStart = start;
|
| - cm.state.fakedLastChar = true;
|
| - }
|
| - cm.state.pasteIncoming = true;
|
| - fastPoll(cm);
|
| - });
|
| + 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); }}
|
| + };
|
|
|
| - function prepareCopyCut(e) {
|
| - if (cm.somethingSelected()) {
|
| - lastCopied = cm.getSelections();
|
| - if (d.inaccurateSelection) {
|
| - d.prevInput = "";
|
| - d.inaccurateSelection = false;
|
| - d.input.value = lastCopied.join("\n");
|
| - selectInput(d.input);
|
| - }
|
| - } else {
|
| - 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));
|
| - }
|
| - if (e.type == "cut") {
|
| - cm.setSelections(ranges, null, sel_dontScroll);
|
| - } else {
|
| - d.prevInput = "";
|
| - d.input.value = text.join("\n");
|
| - selectInput(d.input);
|
| - }
|
| - lastCopied = text;
|
| - }
|
| - if (e.type == "cut") cm.state.cutIncoming = true;
|
| - }
|
| - on(d.input, "cut", prepareCopyCut);
|
| - on(d.input, "copy", prepareCopyCut);
|
| + 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", bind(onFocus, cm));
|
| + on(inp, "blur", bind(onBlur, cm));
|
| + }
|
|
|
| - // Needed to handle Tab key in KHTML
|
| - if (khtml) on(d.sizer, "mouseup", function() {
|
| - if (activeElt() == d.input) d.input.blur();
|
| - focusInput(cm);
|
| - });
|
| + 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);
|
| + }
|
| }
|
|
|
| // Called when the window resizes
|
| @@ -2778,7 +3567,7 @@
|
| // coordinates beyond the right of the text.
|
| function posFromMouse(cm, e, liberal, forRect) {
|
| var display = cm.display;
|
| - if (!liberal && e_target(e).getAttribute("not-content") == "true") 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.
|
| @@ -2798,8 +3587,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)) {
|
| @@ -2817,7 +3606,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);
|
| @@ -2825,18 +3617,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) {
|
| @@ -2850,9 +3644,10 @@
|
| }
|
|
|
| var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
|
| - if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
|
| + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
|
| type == "single" && (contained = sel.contains(start)) > -1 &&
|
| - !sel.ranges[contained].empty())
|
| + (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, modifier);
|
| @@ -2861,7 +3656,7 @@
|
| // 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, modifier) {
|
| - var display = cm.display;
|
| + var display = cm.display, startTime = +new Date;
|
| var dragEnd = operation(cm, function(e2) {
|
| if (webkit) display.scroller.draggable = false;
|
| cm.state.draggingText = false;
|
| @@ -2869,17 +3664,19 @@
|
| off(display.scroller, "drop", dragEnd);
|
| if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
|
| e_preventDefault(e2);
|
| - if (!modifier)
|
| + if (!modifier && +new Date - 200 < startTime)
|
| extendSelection(cm.doc, start);
|
| - focusInput(cm);
|
| - // Work around unexplainable focus problem in IE9 (#2127)
|
| - if (ie && ie_version == 9)
|
| - setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
|
| + // 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);
|
| @@ -2900,9 +3697,10 @@
|
| 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);
|
| @@ -2931,8 +3729,9 @@
|
| ourIndex = ranges.length;
|
| setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
|
| {scroll: false, origin: "*mouse"});
|
| - } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single") {
|
| - setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0));
|
| + } 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);
|
| @@ -2994,7 +3793,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)
|
| @@ -3010,9 +3809,10 @@
|
| }
|
|
|
| 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;
|
| @@ -3023,13 +3823,14 @@
|
| 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;
|
| @@ -3046,14 +3847,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
|
| @@ -3062,23 +3863,32 @@
|
|
|
| function onDrop(e) {
|
| var cm = this;
|
| + clearDragCursor(cm);
|
| if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
|
| return;
|
| e_preventDefault(e);
|
| 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 = operation(cm, function() {
|
| - text[i] = reader.result;
|
| + 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)));
|
| }
|
| @@ -3091,19 +3901,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) {
|
| - if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey))
|
| + 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){}
|
| @@ -3115,6 +3925,7 @@
|
| 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.
|
| @@ -3132,6 +3943,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
|
| @@ -3196,8 +4026,9 @@
|
|
|
| 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.
|
| @@ -3221,10 +4052,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;
|
| }
|
| @@ -3270,10 +4106,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 {
|
| @@ -3300,7 +4136,7 @@
|
| stopSeq.set(50, function() {
|
| if (cm.state.keySeq == seq) {
|
| cm.state.keySeq = null;
|
| - resetInput(cm);
|
| + cm.display.input.reset();
|
| }
|
| });
|
| name = seq + " " + name;
|
| @@ -3352,7 +4188,7 @@
|
| 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 && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
|
| @@ -3393,36 +4229,49 @@
|
|
|
| function onKeyPress(e) {
|
| var cm = this;
|
| - if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) 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_version >= 9) cm.display.inputHasSelection = null;
|
| - fastPoll(cm);
|
| + cm.display.input.onKeyPress(e);
|
| }
|
|
|
| // FOCUS/BLUR EVENTS
|
|
|
| + function delayBlurEvent(cm) {
|
| + cm.state.delayingBlurEvent = true;
|
| + setTimeout(function() {
|
| + if (cm.state.delayingBlurEvent) {
|
| + cm.state.delayingBlurEvent = false;
|
| + onBlur(cm);
|
| + }
|
| + }, 100);
|
| + }
|
| +
|
| function onFocus(cm) {
|
| + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false;
|
| +
|
| if (cm.options.readOnly == "nocursor") return;
|
| if (!cm.state.focused) {
|
| signal(cm, "focus", cm);
|
| cm.state.focused = true;
|
| addClass(cm.display.wrapper, "CodeMirror-focused");
|
| - // The prevInput test prevents this from firing when a context
|
| - // menu is closed (since the resetInput would kill the
|
| + // 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) {
|
| - resetInput(cm);
|
| - if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
|
| + 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) {
|
| + if (cm.state.delayingBlurEvent) return;
|
| +
|
| if (cm.state.focused) {
|
| signal(cm, "blur", cm);
|
| cm.state.focused = false;
|
| @@ -3438,85 +4287,14 @@
|
| // 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;
|
| - 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);";
|
| - if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
|
| - focusInput(cm);
|
| - if (webkit) window.scrollTo(null, oldScrollY);
|
| - resetInput(cm);
|
| - // Adds "Select all" to context menu in FF
|
| - if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
|
| - display.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 (display.input.selectionStart != null) {
|
| - var selected = cm.somethingSelected();
|
| - var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
|
| - display.prevInput = selected ? "" : "\u200b";
|
| - display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
|
| - // Re-set this, in case some other handler touched the
|
| - // selection in the meantime.
|
| - display.selForContextMenu = cm.doc.sel;
|
| - }
|
| - }
|
| - function rehide() {
|
| - display.contextMenuPending = false;
|
| - display.inputDiv.style.position = "relative";
|
| - display.input.style.cssText = oldCSS;
|
| - if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos);
|
| - slowPoll(cm);
|
| -
|
| - // Try to detect the user choosing select-all
|
| - if (display.input.selectionStart != null) {
|
| - if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
|
| - var i = 0, poll = function() {
|
| - if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
|
| - operation(cm, commands.selectAll)(cm);
|
| - else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
|
| - else resetInput(cm);
|
| - };
|
| - 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);
|
| - }
|
| + 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
|
| @@ -3644,7 +4422,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;
|
| @@ -3820,7 +4598,7 @@
|
| 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});
|
| }
|
|
|
| @@ -3999,6 +4777,8 @@
|
|
|
| if (indentString != curSpaceString) {
|
| 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.
|
| @@ -4011,7 +4791,6 @@
|
| }
|
| }
|
| }
|
| - line.stateAfter = null;
|
| }
|
|
|
| // Utility for applying a change to a line by handle or number,
|
| @@ -4063,10 +4842,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);
|
| }
|
| @@ -4076,14 +4854,16 @@
|
| 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) {
|
| @@ -4103,8 +4883,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;
|
| }
|
|
|
| @@ -4140,7 +4920,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];
|
| @@ -4251,7 +5031,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]]);
|
| @@ -4301,10 +5081,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);
|
| },
|
| @@ -4333,12 +5118,6 @@
|
| });
|
| }),
|
|
|
| - addLineWidget: methodOp(function(handle, node, options) {
|
| - return addLineWidget(this, handle, node, options);
|
| - }),
|
| -
|
| - removeLineWidget: function(widget) { widget.clear(); },
|
| -
|
| lineInfo: function(line) {
|
| if (typeof line == "number") {
|
| if (!isLine(this.doc, line)) return null;
|
| @@ -4362,6 +5141,7 @@
|
| 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;
|
| @@ -4396,9 +5176,11 @@
|
|
|
| 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; }
|
| @@ -4488,7 +5270,8 @@
|
|
|
| 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);
|
| @@ -4564,14 +5347,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;}
|
| @@ -4612,12 +5395,31 @@
|
| clearCaches(cm);
|
| regChange(cm);
|
| }, true);
|
| - option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
|
| - cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
|
| - cm.refresh();
|
| - }, true);
|
| + option("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("rtlMoveVisually", !windows);
|
| option("wholeLineUpdateBefore", true);
|
|
|
| @@ -4658,6 +5460,7 @@
|
| option("showCursorWhenSelecting", false, updateSelection, true);
|
|
|
| option("resetSelectionOnContextMenu", true);
|
| + option("lineWiseCopyCut", true);
|
|
|
| option("readOnly", false, function(cm, val) {
|
| if (val == "nocursor") {
|
| @@ -4666,11 +5469,12 @@
|
| 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);
|
| @@ -4686,11 +5490,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);
|
|
|
| @@ -4936,7 +5740,7 @@
|
| 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(new Array(tabSize - col % tabSize + 1).join(" "));
|
| + spaces.push(spaceStr(tabSize - col % tabSize));
|
| }
|
| cm.replaceSelections(spaces);
|
| },
|
| @@ -4958,7 +5762,8 @@
|
| } else if (cur.line > cm.doc.first) {
|
| var prev = getLine(cm.doc, cur.line - 1).text;
|
| if (prev)
|
| - cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1),
|
| + 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");
|
| }
|
| }
|
| @@ -4972,12 +5777,13 @@
|
| 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();}
|
| };
|
|
|
| @@ -5012,7 +5818,8 @@
|
| "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",
|
| @@ -5062,7 +5869,7 @@
|
| for (var i = 0; i < keys.length; i++) {
|
| var val, name;
|
| if (i == keys.length - 1) {
|
| - name = keyname;
|
| + name = keys.join(" ");
|
| val = value;
|
| } else {
|
| name = keys.slice(0, i + 1).join(" ");
|
| @@ -5121,10 +5928,10 @@
|
| // 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
|
| @@ -5152,23 +5959,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() {
|
| - 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;
|
| - }
|
| - };
|
| return cm;
|
| };
|
|
|
| @@ -5261,10 +6071,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);
|
|
|
| @@ -5768,8 +6581,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) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) ||
|
| - fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight)))
|
| + 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;
|
| }
|
| }
|
| @@ -5848,10 +6661,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);
|
| @@ -5862,52 +6675,55 @@
|
| }
|
|
|
| LineWidget.prototype.clear = function() {
|
| - var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
|
| + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
|
| if (no == null || !ws) return;
|
| for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
|
| if (!ws.length) line.widgets = null;
|
| var height = widgetHeight(this);
|
| - runInOp(cm, function() {
|
| + updateLineHeight(line, Math.max(0, line.height - height));
|
| + if (cm) runInOp(cm, function() {
|
| adjustScrollWhenAboveVisible(cm, line, -height);
|
| regLineChange(cm, no, "widget");
|
| - updateLineHeight(line, Math.max(0, line.height - height));
|
| });
|
| };
|
| LineWidget.prototype.changed = function() {
|
| - var oldH = this.height, cm = this.cm, line = this.line;
|
| + var oldH = this.height, cm = this.doc.cm, line = this.line;
|
| this.height = null;
|
| var diff = widgetHeight(this) - oldH;
|
| if (!diff) return;
|
| - runInOp(cm, function() {
|
| + updateLineHeight(line, line.height + diff);
|
| + if (cm) runInOp(cm, function() {
|
| cm.curOp.forceUpdate = true;
|
| adjustScrollWhenAboveVisible(cm, line, diff);
|
| - updateLineHeight(line, line.height + diff);
|
| });
|
| };
|
|
|
| function widgetHeight(widget) {
|
| if (widget.height != null) return widget.height;
|
| + var cm = widget.doc.cm;
|
| + if (!cm) return 0;
|
| if (!contains(document.body, widget.node)) {
|
| var parentStyle = "position: relative;";
|
| if (widget.coverGutter)
|
| - parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth + "px;";
|
| + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;";
|
| if (widget.noHScroll)
|
| - parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;";
|
| - removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
|
| + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
|
| + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
|
| }
|
| - return widget.height = widget.node.offsetHeight;
|
| + return widget.height = widget.node.parentNode.offsetHeight;
|
| }
|
|
|
| - function addLineWidget(cm, handle, node, options) {
|
| - var widget = new LineWidget(cm, node, options);
|
| - if (widget.noHScroll) cm.display.alignWidgets = true;
|
| - changeLine(cm.doc, handle, "widget", function(line) {
|
| + function addLineWidget(doc, handle, node, options) {
|
| + var widget = new LineWidget(doc, node, options);
|
| + var cm = doc.cm;
|
| + if (cm && widget.noHScroll) cm.display.alignWidgets = true;
|
| + changeLine(doc, handle, "widget", function(line) {
|
| var widgets = line.widgets || (line.widgets = []);
|
| if (widget.insertAt == null) widgets.push(widget);
|
| else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
|
| widget.line = line;
|
| - if (!lineIsHidden(cm.doc, line)) {
|
| - var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
|
| + if (cm && !lineIsHidden(doc, line)) {
|
| + var aboveVisible = heightAtLine(line) < doc.scrollTop;
|
| updateLineHeight(line, line.height + widgetHeight(widget));
|
| if (aboveVisible) addToScrollPos(cm, null, widget.height);
|
| cm.curOp.forceUpdate = true;
|
| @@ -6083,7 +6899,9 @@
|
|
|
| function getLineStyles(cm, line, updateFrontier) {
|
| if (!line.styles || line.styles[0] != cm.state.modeGen) {
|
| - var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
|
| + 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;
|
| @@ -6100,7 +6918,7 @@
|
| var stream = new StringStream(text, cm.options.tabSize);
|
| stream.start = stream.pos = startAt || 0;
|
| if (text == "") callBlankLine(mode, state);
|
| - while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
|
| + while (!stream.eol()) {
|
| readToken(mode, stream, state);
|
| stream.start = stream.pos;
|
| }
|
| @@ -6127,7 +6945,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.
|
| @@ -6137,8 +6958,6 @@
|
| builder.addToken = buildToken;
|
| // Optionally wire in some hacks into the token-rendering
|
| // algorithm, to deal with browser quirks.
|
| - if ((ie || webkit) && cm.getOption("lineWrapping"))
|
| - builder.addToken = buildTokenSplitSpaces(builder.addToken);
|
| if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
|
| builder.addToken = buildTokenBadBidi(builder.addToken, order);
|
| builder.map = [];
|
| @@ -6166,8 +6985,11 @@
|
| }
|
|
|
| // See issue #2901
|
| - if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className))
|
| - builder.content.className = "cm-tab-wrap-hack";
|
| + 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)
|
| @@ -6179,6 +7001,7 @@
|
| 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;
|
| }
|
|
|
| @@ -6186,10 +7009,11 @@
|
| // the line map. Takes care to render special characters separately.
|
| function buildToken(builder, text, style, startStyle, endStyle, title, css) {
|
| if (!text) return;
|
| - var special = builder.cm.options.specialChars, mustWrap = false;
|
| + var displayText = builder.splitSpaces ? 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 && ie_version < 9) mustWrap = true;
|
| builder.pos += text.length;
|
| @@ -6200,7 +7024,7 @@
|
| var m = special.exec(text);
|
| var skipped = m ? m.index - pos : text.length - pos;
|
| if (skipped) {
|
| - var txt = document.createTextNode(text.slice(pos, pos + skipped));
|
| + var txt = document.createTextNode(displayText.slice(pos, pos + skipped));
|
| if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
|
| else content.appendChild(txt);
|
| builder.map.push(builder.pos, builder.pos + skipped, txt);
|
| @@ -6212,9 +7036,16 @@
|
| 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]);
|
| + txt.setAttribute("cm-text", m[0]);
|
| if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
|
| else content.appendChild(txt);
|
| builder.col += 1;
|
| @@ -6223,6 +7054,7 @@
|
| builder.pos++;
|
| }
|
| }
|
| + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32
|
| if (style || startStyle || endStyle || mustWrap || css) {
|
| var fullStyle = style || "";
|
| if (startStyle) fullStyle += startStyle;
|
| @@ -6234,22 +7066,23 @@
|
| 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 (;;) {
|
| @@ -6258,8 +7091,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;
|
| @@ -6269,11 +7102,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
|
| @@ -6292,30 +7132,38 @@
|
| if (nextChange == pos) { // Update current marker set
|
| 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 = m.css;
|
| + 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;
|
|
|
| @@ -6501,13 +7349,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;
|
| @@ -6527,7 +7378,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);
|
| @@ -6551,8 +7402,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)])]);
|
| @@ -6566,8 +7417,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);
|
| };
|
| @@ -6597,12 +7450,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", full: true}, true);
|
| + text: this.splitLines(code), origin: "setValue", full: true}, true);
|
| setSelection(this, simpleSelection(top));
|
| }),
|
| replaceRange: function(code, from, to, origin) {
|
| @@ -6613,7 +7466,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;},
|
| @@ -6653,10 +7506,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;
|
| @@ -6679,13 +7533,13 @@
|
| 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;
|
| @@ -6700,7 +7554,7 @@
|
| 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--)
|
| @@ -6775,13 +7629,19 @@
|
| });
|
| }),
|
|
|
| + addLineWidget: docMethodOp(function(handle, node, options) {
|
| + return addLineWidget(this, handle, node, options);
|
| + }),
|
| + removeLineWidget: function(widget) { widget.clear(); },
|
| +
|
| markText: function(from, to, options) {
|
| - return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
|
| + 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");
|
| },
|
| @@ -6803,9 +7663,9 @@
|
| 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);
|
| }
|
| @@ -6824,9 +7684,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;
|
| @@ -6837,14 +7697,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;
|
| @@ -6860,7 +7722,7 @@
|
| 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}];
|
| @@ -6889,14 +7751,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);};
|
| @@ -7329,24 +8197,30 @@
|
| }
|
| };
|
|
|
| + 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;
|
| @@ -7359,8 +8233,8 @@
|
| // them to be executed when the last operation ends, or, if no
|
| // operation is active, when a timeout fires.
|
| function signalLater(emitter, type /*, values...*/) {
|
| - var arr = emitter._handlers && emitter._handlers[type];
|
| - if (!arr) return;
|
| + var arr = getHandlers(emitter, type, false)
|
| + if (!arr.length) return;
|
| var args = Array.prototype.slice.call(arguments, 2), list;
|
| if (operationGroup) {
|
| list = operationGroup.delayedCallbacks;
|
| @@ -7400,8 +8274,7 @@
|
| }
|
|
|
| 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
|
| @@ -7448,7 +8321,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;
|
| @@ -7488,14 +8361,15 @@
|
| return out;
|
| }
|
|
|
| + 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;
|
| @@ -7514,7 +8388,7 @@
|
| return function(){return f.apply(null, args);};
|
| }
|
|
|
| - var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
|
| + 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));
|
| @@ -7550,9 +8424,9 @@
|
| }
|
|
|
| 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;
|
| };
|
| @@ -7576,14 +8450,23 @@
|
| 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 && ie_version < 11) activeElt = function() {
|
| @@ -7666,8 +8549,10 @@
|
| if (measure.firstChild.offsetHeight != 0)
|
| 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
|
| @@ -7676,14 +8561,15 @@
|
| if (badBidiRects != null) return badBidiRects;
|
| var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
|
| var r0 = range(txt, 0, 1).getBoundingClientRect();
|
| - if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
|
| 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);
|
| @@ -7729,14 +8615,16 @@
|
|
|
| // 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);
|
| @@ -8030,6 +8918,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));
|
|
|
| @@ -8039,7 +8929,7 @@
|
|
|
| // THE END
|
|
|
| - CodeMirror.version = "4.12.0";
|
| + CodeMirror.version = "5.17.1";
|
|
|
| return CodeMirror;
|
| -});
|
| +});
|
|
|