codemirror-rails 4.13 → 5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codemirror/rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/codemirror.js +1025 -448
  4. data/vendor/assets/javascripts/codemirror/addons/edit/matchbrackets.js +1 -1
  5. data/vendor/assets/stylesheets/codemirror.css +13 -2
  6. data/vendor/assets/stylesheets/codemirror/themes/3024-day.css +2 -0
  7. data/vendor/assets/stylesheets/codemirror/themes/3024-night.css +2 -0
  8. data/vendor/assets/stylesheets/codemirror/themes/ambiance.css +4 -6
  9. data/vendor/assets/stylesheets/codemirror/themes/base16-dark.css +2 -0
  10. data/vendor/assets/stylesheets/codemirror/themes/base16-light.css +2 -0
  11. data/vendor/assets/stylesheets/codemirror/themes/blackboard.css +2 -0
  12. data/vendor/assets/stylesheets/codemirror/themes/cobalt.css +2 -0
  13. data/vendor/assets/stylesheets/codemirror/themes/erlang-dark.css +2 -0
  14. data/vendor/assets/stylesheets/codemirror/themes/lesser-dark.css +2 -0
  15. data/vendor/assets/stylesheets/codemirror/themes/mbo.css +2 -0
  16. data/vendor/assets/stylesheets/codemirror/themes/mdn-like.css +2 -0
  17. data/vendor/assets/stylesheets/codemirror/themes/midnight.css +2 -0
  18. data/vendor/assets/stylesheets/codemirror/themes/monokai.css +2 -0
  19. data/vendor/assets/stylesheets/codemirror/themes/night.css +2 -0
  20. data/vendor/assets/stylesheets/codemirror/themes/paraiso-dark.css +2 -0
  21. data/vendor/assets/stylesheets/codemirror/themes/paraiso-light.css +2 -0
  22. data/vendor/assets/stylesheets/codemirror/themes/pastel-on-dark.css +3 -0
  23. data/vendor/assets/stylesheets/codemirror/themes/rubyblue.css +2 -0
  24. data/vendor/assets/stylesheets/codemirror/themes/solarized.css +6 -6
  25. data/vendor/assets/stylesheets/codemirror/themes/the-matrix.css +2 -0
  26. data/vendor/assets/stylesheets/codemirror/themes/tomorrow-night-eighties.css +2 -0
  27. data/vendor/assets/stylesheets/codemirror/themes/twilight.css +2 -0
  28. data/vendor/assets/stylesheets/codemirror/themes/vibrant-ink.css +2 -0
  29. data/vendor/assets/stylesheets/codemirror/themes/xq-dark.css +2 -0
  30. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 893503095237028fea620f11e8cc5a6eca7873b5
4
- data.tar.gz: 97b7ef4412832ae76014e97a63b6b213b00e57be
3
+ metadata.gz: ee58ad7606652b7d8522d0aa8e8bebba57fefa10
4
+ data.tar.gz: 23919e1e33923f699a4198fe5244d1018db115c7
5
5
  SHA512:
6
- metadata.gz: 43a90310c194f85795aeede4d02fbfab6a34b3ae68523925fa3073ea1479cbabf1d87610abb4cb475dc327f18fd03c407142907167a02950772e9a2548829e0b
7
- data.tar.gz: 2ffd75777cb370c1fbd861214816b89cb06b9b00e6e1833058b667dc0dfa14df482c212926e90e85867f3f9032897ad5130ee2c97758de8b5af0f3a888500353
6
+ metadata.gz: e40c4eec1f07c990ab495b1a316f51c41645ad87ad159b62bc45061ade6dff34ea60cea1902adc1e5e38839f5179facccbb3041b8156549907fb5a72a00253f6
7
+ data.tar.gz: 4e4b07ea9cd230ae38699c606f2810537e5ee9d0fe546c3a188749137c3124359ba112eb85245404f83e1cd4f8e999a4cf836e816fc7f69b34d3158c31c0a22b
@@ -1,6 +1,6 @@
1
1
  module Codemirror
2
2
  module Rails
3
- VERSION = '4.13'
4
- CODEMIRROR_VERSION = '4.13'
3
+ VERSION = '5.0'
4
+ CODEMIRROR_VERSION = '5.0'
5
5
  end
6
6
  end
@@ -23,7 +23,6 @@
23
23
  // detected are enabled based on userAgent etc sniffing.
24
24
 
25
25
  var gecko = /gecko\/\d/i.test(navigator.userAgent);
26
- // ie_uptoN means Internet Explorer version N or lower
27
26
  var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
28
27
  var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
29
28
  var ie = ie_upto10 || ie_11up;
@@ -33,7 +32,6 @@
33
32
  var chrome = /Chrome\//.test(navigator.userAgent);
34
33
  var presto = /Opera\//.test(navigator.userAgent);
35
34
  var safari = /Apple Computer/.test(navigator.vendor);
36
- var khtml = /KHTML\//.test(navigator.userAgent);
37
35
  var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
38
36
  var phantom = /PhantomJS/.test(navigator.userAgent);
39
37
 
@@ -70,13 +68,14 @@
70
68
  if (typeof doc == "string") doc = new Doc(doc, options.mode);
71
69
  this.doc = doc;
72
70
 
73
- var display = this.display = new Display(place, doc);
71
+ var input = new CodeMirror.inputStyles[options.inputStyle](this);
72
+ var display = this.display = new Display(place, doc, input);
74
73
  display.wrapper.CodeMirror = this;
75
74
  updateGutters(this);
76
75
  themeChanged(this);
77
76
  if (options.lineWrapping)
78
77
  this.display.wrapper.className += " CodeMirror-wrap";
79
- if (options.autofocus && !mobile) focusInput(this);
78
+ if (options.autofocus && !mobile) display.input.focus();
80
79
  initScrollbars(this);
81
80
 
82
81
  this.state = {
@@ -85,15 +84,17 @@
85
84
  modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
86
85
  overwrite: false, focused: false,
87
86
  suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
88
- pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
87
+ pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
89
88
  draggingText: false,
90
89
  highlight: new Delayed(), // stores highlight worker timeout
91
90
  keySeq: null // Unfinished key sequence
92
91
  };
93
92
 
93
+ var cm = this;
94
+
94
95
  // Override magic textarea content restore that IE sometimes does
95
96
  // on our hidden textarea on reload
96
- if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20);
97
+ if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20);
97
98
 
98
99
  registerEventHandlers(this);
99
100
  ensureGlobalHandlers();
@@ -102,7 +103,7 @@
102
103
  this.curOp.forceUpdate = true;
103
104
  attachDoc(this, doc);
104
105
 
105
- if ((options.autofocus && !mobile) || activeElt() == display.input)
106
+ if ((options.autofocus && !mobile) || cm.hasFocus())
106
107
  setTimeout(bind(onFocus, this), 20);
107
108
  else
108
109
  onBlur(this);
@@ -126,31 +127,17 @@
126
127
  // and content drawing. It holds references to DOM nodes and
127
128
  // display-related state.
128
129
 
129
- function Display(place, doc) {
130
+ function Display(place, doc, input) {
130
131
  var d = this;
132
+ this.input = input;
131
133
 
132
- // The semihidden textarea that is focused when the editor is
133
- // focused, and receives input.
134
- var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
135
- // The textarea is kept positioned near the cursor to prevent the
136
- // fact that it'll be scrolled into view on input from scrolling
137
- // our fake cursor out of view. On webkit, when wrap=off, paste is
138
- // very slow. So make the area wide instead.
139
- if (webkit) input.style.width = "1000px";
140
- else input.setAttribute("wrap", "off");
141
- // If border: 0; -- iOS fails to open keyboard (issue #1287)
142
- if (ios) input.style.border = "1px solid black";
143
- input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
144
-
145
- // Wraps and hides input textarea
146
- d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
147
134
  // Covers bottom-right square when both scrollbars are present.
148
135
  d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
149
- d.scrollbarFiller.setAttribute("not-content", "true");
136
+ d.scrollbarFiller.setAttribute("cm-not-content", "true");
150
137
  // Covers bottom of gutter when coverGutterNextToScrollbar is on
151
138
  // and h scrollbar is present.
152
139
  d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
153
- d.gutterFiller.setAttribute("not-content", "true");
140
+ d.gutterFiller.setAttribute("cm-not-content", "true");
154
141
  // Will contain the actual code, positioned to cover the viewport.
155
142
  d.lineDiv = elt("div", null, "CodeMirror-code");
156
143
  // Elements are added to these to represent selection and cursors.
@@ -179,15 +166,11 @@
179
166
  d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
180
167
  d.scroller.setAttribute("tabIndex", "-1");
181
168
  // The element in which the editor lives.
182
- d.wrapper = elt("div", [d.inputDiv, d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
169
+ d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
183
170
 
184
171
  // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
185
172
  if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
186
- // Needed to hide big blue blinking cursor on Mobile Safari
187
- if (ios) input.style.width = "0px";
188
- if (!webkit) d.scroller.draggable = true;
189
- // Needed to handle Tab key in KHTML
190
- if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
173
+ if (!webkit && !(gecko && mobile)) d.scroller.draggable = true;
191
174
 
192
175
  if (place) {
193
176
  if (place.appendChild) place.appendChild(d.wrapper);
@@ -214,25 +197,13 @@
214
197
  // Used to only resize the line number gutter when necessary (when
215
198
  // the amount of lines crosses a boundary that makes its width change)
216
199
  d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
217
- // See readInput and resetInput
218
- d.prevInput = "";
219
200
  // Set to true when a non-horizontal-scrolling line widget is
220
201
  // added. As an optimization, line widget aligning is skipped when
221
202
  // this is false.
222
203
  d.alignWidgets = false;
223
- // Flag that indicates whether we expect input to appear real soon
224
- // now (after some event like 'keypress' or 'input') and are
225
- // polling intensively.
226
- d.pollingFast = false;
227
- // Self-resetting timeout for the poller
228
- d.poll = new Delayed();
229
204
 
230
205
  d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
231
206
 
232
- // Tracks when resetInput has punted to just putting a short
233
- // string into the textarea instead of the full selection.
234
- d.inaccurateSelection = false;
235
-
236
207
  // Tracks the maximum line length so that the horizontal scrollbar
237
208
  // can be kept static when scrolling.
238
209
  d.maxLine = null;
@@ -248,6 +219,10 @@
248
219
  // Used to track whether anything happened since the context menu
249
220
  // was opened.
250
221
  d.selForContextMenu = null;
222
+
223
+ d.activeTouch = null;
224
+
225
+ input.init(d);
251
226
  }
252
227
 
253
228
  // STATE UPDATES
@@ -515,10 +490,11 @@
515
490
 
516
491
  cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) {
517
492
  cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);
493
+ // Prevent clicks in the scrollbars from killing focus
518
494
  on(node, "mousedown", function() {
519
- if (cm.state.focused) setTimeout(bind(focusInput, cm), 0);
495
+ if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0);
520
496
  });
521
- node.setAttribute("not-content", "true");
497
+ node.setAttribute("cm-not-content", "true");
522
498
  }, function(pos, axis) {
523
499
  if (axis == "horizontal") setScrollLeft(cm, pos);
524
500
  else setScrollTop(cm, pos);
@@ -873,7 +849,7 @@
873
849
  for (var i = 0; i < view.length; i++) {
874
850
  var lineView = view[i];
875
851
  if (lineView.hidden) {
876
- } else if (!lineView.node) { // Not drawn yet
852
+ } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
877
853
  var node = buildLineElement(cm, lineView, lineN, dims);
878
854
  container.insertBefore(node, cur);
879
855
  } else { // Already drawn
@@ -904,7 +880,7 @@
904
880
  if (type == "text") updateLineText(cm, lineView);
905
881
  else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
906
882
  else if (type == "class") updateLineClasses(lineView);
907
- else if (type == "widget") updateLineWidgets(lineView, dims);
883
+ else if (type == "widget") updateLineWidgets(cm, lineView, dims);
908
884
  }
909
885
  lineView.changes = null;
910
886
  }
@@ -982,11 +958,11 @@
982
958
  var markers = lineView.line.gutterMarkers;
983
959
  if (cm.options.lineNumbers || markers) {
984
960
  var wrap = ensureLineWrapped(lineView);
985
- var gutterWrap = lineView.gutter =
986
- wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
987
- (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
988
- "px; width: " + dims.gutterTotalWidth + "px"),
989
- lineView.text);
961
+ var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
962
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
963
+ "px; width: " + dims.gutterTotalWidth + "px");
964
+ cm.display.input.setUneditable(gutterWrap);
965
+ wrap.insertBefore(gutterWrap, lineView.text);
990
966
  if (lineView.line.gutterClass)
991
967
  gutterWrap.className += " " + lineView.line.gutterClass;
992
968
  if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
@@ -1004,14 +980,14 @@
1004
980
  }
1005
981
  }
1006
982
 
1007
- function updateLineWidgets(lineView, dims) {
983
+ function updateLineWidgets(cm, lineView, dims) {
1008
984
  if (lineView.alignable) lineView.alignable = null;
1009
985
  for (var node = lineView.node.firstChild, next; node; node = next) {
1010
986
  var next = node.nextSibling;
1011
987
  if (node.className == "CodeMirror-linewidget")
1012
988
  lineView.node.removeChild(node);
1013
989
  }
1014
- insertLineWidgets(lineView, dims);
990
+ insertLineWidgets(cm, lineView, dims);
1015
991
  }
1016
992
 
1017
993
  // Build a line's DOM representation from scratch
@@ -1023,25 +999,26 @@
1023
999
 
1024
1000
  updateLineClasses(lineView);
1025
1001
  updateLineGutter(cm, lineView, lineN, dims);
1026
- insertLineWidgets(lineView, dims);
1002
+ insertLineWidgets(cm, lineView, dims);
1027
1003
  return lineView.node;
1028
1004
  }
1029
1005
 
1030
1006
  // A lineView may contain multiple logical lines (when merged by
1031
1007
  // collapsed spans). The widgets for all of them need to be drawn.
1032
- function insertLineWidgets(lineView, dims) {
1033
- insertLineWidgetsFor(lineView.line, lineView, dims, true);
1008
+ function insertLineWidgets(cm, lineView, dims) {
1009
+ insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);
1034
1010
  if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
1035
- insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
1011
+ insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false);
1036
1012
  }
1037
1013
 
1038
- function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
1014
+ function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
1039
1015
  if (!line.widgets) return;
1040
1016
  var wrap = ensureLineWrapped(lineView);
1041
1017
  for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
1042
1018
  var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
1043
1019
  if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true");
1044
1020
  positionLineWidget(widget, node, lineView, dims);
1021
+ cm.display.input.setUneditable(node);
1045
1022
  if (allowAbove && widget.above)
1046
1023
  wrap.insertBefore(node, lineView.gutter || lineView.text);
1047
1024
  else
@@ -1084,6 +1061,852 @@
1084
1061
  function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
1085
1062
  function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
1086
1063
 
1064
+ // INPUT HANDLING
1065
+
1066
+ function ensureFocus(cm) {
1067
+ if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
1068
+ }
1069
+
1070
+ function isReadOnly(cm) {
1071
+ return cm.options.readOnly || cm.doc.cantEdit;
1072
+ }
1073
+
1074
+ // This will be set to an array of strings when copying, so that,
1075
+ // when pasting, we know what kind of selections the copied text
1076
+ // was made out of.
1077
+ var lastCopied = null;
1078
+
1079
+ function applyTextInput(cm, inserted, deleted, sel) {
1080
+ var doc = cm.doc;
1081
+ cm.display.shift = false;
1082
+ if (!sel) sel = doc.sel;
1083
+
1084
+ var textLines = splitLines(inserted), multiPaste = null;
1085
+ // When pasing N lines into N selections, insert one line per selection
1086
+ if (cm.state.pasteIncoming && sel.ranges.length > 1) {
1087
+ if (lastCopied && lastCopied.join("\n") == inserted)
1088
+ multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
1089
+ else if (textLines.length == sel.ranges.length)
1090
+ multiPaste = map(textLines, function(l) { return [l]; });
1091
+ }
1092
+
1093
+ // Normal behavior is to insert the new text into every selection
1094
+ for (var i = sel.ranges.length - 1; i >= 0; i--) {
1095
+ var range = sel.ranges[i];
1096
+ var from = range.from(), to = range.to();
1097
+ if (range.empty()) {
1098
+ if (deleted && deleted > 0) // Handle deletion
1099
+ from = Pos(from.line, from.ch - deleted);
1100
+ else if (cm.state.overwrite && !cm.state.pasteIncoming) // Handle overwrite
1101
+ to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
1102
+ }
1103
+ var updateInput = cm.curOp.updateInput;
1104
+ var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
1105
+ origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
1106
+ makeChange(cm.doc, changeEvent);
1107
+ signalLater(cm, "inputRead", cm, changeEvent);
1108
+ // When an 'electric' character is inserted, immediately trigger a reindent
1109
+ if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
1110
+ cm.options.smartIndent && range.head.ch < 100 &&
1111
+ (!i || sel.ranges[i - 1].head.line != range.head.line)) {
1112
+ var mode = cm.getModeAt(range.head);
1113
+ var end = changeEnd(changeEvent);
1114
+ if (mode.electricChars) {
1115
+ for (var j = 0; j < mode.electricChars.length; j++)
1116
+ if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
1117
+ indentLine(cm, end.line, "smart");
1118
+ break;
1119
+ }
1120
+ } else if (mode.electricInput) {
1121
+ if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
1122
+ indentLine(cm, end.line, "smart");
1123
+ }
1124
+ }
1125
+ }
1126
+ ensureCursorVisible(cm);
1127
+ cm.curOp.updateInput = updateInput;
1128
+ cm.curOp.typing = true;
1129
+ cm.state.pasteIncoming = cm.state.cutIncoming = false;
1130
+ }
1131
+
1132
+ function copyableRanges(cm) {
1133
+ var text = [], ranges = [];
1134
+ for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
1135
+ var line = cm.doc.sel.ranges[i].head.line;
1136
+ var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
1137
+ ranges.push(lineRange);
1138
+ text.push(cm.getRange(lineRange.anchor, lineRange.head));
1139
+ }
1140
+ return {text: text, ranges: ranges};
1141
+ }
1142
+
1143
+ function disableBrowserMagic(field) {
1144
+ field.setAttribute("autocorrect", "off");
1145
+ field.setAttribute("autocapitalize", "off");
1146
+ field.setAttribute("spellcheck", "false");
1147
+ }
1148
+
1149
+ // TEXTAREA INPUT STYLE
1150
+
1151
+ function TextareaInput(cm) {
1152
+ this.cm = cm;
1153
+ // See input.poll and input.reset
1154
+ this.prevInput = "";
1155
+
1156
+ // Flag that indicates whether we expect input to appear real soon
1157
+ // now (after some event like 'keypress' or 'input') and are
1158
+ // polling intensively.
1159
+ this.pollingFast = false;
1160
+ // Self-resetting timeout for the poller
1161
+ this.polling = new Delayed();
1162
+ // Tracks when input.reset has punted to just putting a short
1163
+ // string into the textarea instead of the full selection.
1164
+ this.inaccurateSelection = false;
1165
+ // Used to work around IE issue with selection being forgotten when focus moves away from textarea
1166
+ this.hasSelection = false;
1167
+ };
1168
+
1169
+ function hiddenTextarea() {
1170
+ var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
1171
+ var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
1172
+ // The textarea is kept positioned near the cursor to prevent the
1173
+ // fact that it'll be scrolled into view on input from scrolling
1174
+ // our fake cursor out of view. On webkit, when wrap=off, paste is
1175
+ // very slow. So make the area wide instead.
1176
+ if (webkit) te.style.width = "1000px";
1177
+ else te.setAttribute("wrap", "off");
1178
+ // If border: 0; -- iOS fails to open keyboard (issue #1287)
1179
+ if (ios) te.style.border = "1px solid black";
1180
+ disableBrowserMagic(te);
1181
+ return div;
1182
+ }
1183
+
1184
+ TextareaInput.prototype = copyObj({
1185
+ init: function(display) {
1186
+ var input = this, cm = this.cm;
1187
+
1188
+ // Wraps and hides input textarea
1189
+ var div = this.wrapper = hiddenTextarea();
1190
+ // The semihidden textarea that is focused when the editor is
1191
+ // focused, and receives input.
1192
+ var te = this.textarea = div.firstChild;
1193
+ display.wrapper.insertBefore(div, display.wrapper.firstChild);
1194
+
1195
+ // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
1196
+ if (ios) te.style.width = "0px";
1197
+
1198
+ on(te, "input", function() {
1199
+ if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null;
1200
+ input.poll();
1201
+ });
1202
+
1203
+ on(te, "paste", function() {
1204
+ // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1205
+ // Add a char to the end of textarea before paste occur so that
1206
+ // selection doesn't span to the end of textarea.
1207
+ if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
1208
+ var start = te.selectionStart, end = te.selectionEnd;
1209
+ te.value += "$";
1210
+ // The selection end needs to be set before the start, otherwise there
1211
+ // can be an intermediate non-empty selection between the two, which
1212
+ // can override the middle-click paste buffer on linux and cause the
1213
+ // wrong thing to get pasted.
1214
+ te.selectionEnd = end;
1215
+ te.selectionStart = start;
1216
+ cm.state.fakedLastChar = true;
1217
+ }
1218
+ cm.state.pasteIncoming = true;
1219
+ input.fastPoll();
1220
+ });
1221
+
1222
+ function prepareCopyCut(e) {
1223
+ if (cm.somethingSelected()) {
1224
+ lastCopied = cm.getSelections();
1225
+ if (input.inaccurateSelection) {
1226
+ input.prevInput = "";
1227
+ input.inaccurateSelection = false;
1228
+ te.value = lastCopied.join("\n");
1229
+ selectInput(te);
1230
+ }
1231
+ } else {
1232
+ var ranges = copyableRanges(cm);
1233
+ lastCopied = ranges.text;
1234
+ if (e.type == "cut") {
1235
+ cm.setSelections(ranges.ranges, null, sel_dontScroll);
1236
+ } else {
1237
+ input.prevInput = "";
1238
+ te.value = ranges.text.join("\n");
1239
+ selectInput(te);
1240
+ }
1241
+ }
1242
+ if (e.type == "cut") cm.state.cutIncoming = true;
1243
+ }
1244
+ on(te, "cut", prepareCopyCut);
1245
+ on(te, "copy", prepareCopyCut);
1246
+
1247
+ on(display.scroller, "paste", function(e) {
1248
+ if (eventInWidget(display, e)) return;
1249
+ cm.state.pasteIncoming = true;
1250
+ input.focus();
1251
+ });
1252
+
1253
+ // Prevent normal selection in the editor (we handle our own)
1254
+ on(display.lineSpace, "selectstart", function(e) {
1255
+ if (!eventInWidget(display, e)) e_preventDefault(e);
1256
+ });
1257
+ },
1258
+
1259
+ prepareSelection: function() {
1260
+ // Redraw the selection and/or cursor
1261
+ var cm = this.cm, display = cm.display, doc = cm.doc;
1262
+ var result = prepareSelection(cm);
1263
+
1264
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
1265
+ if (cm.options.moveInputWithCursor) {
1266
+ var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
1267
+ var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
1268
+ result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
1269
+ headPos.top + lineOff.top - wrapOff.top));
1270
+ result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
1271
+ headPos.left + lineOff.left - wrapOff.left));
1272
+ }
1273
+
1274
+ return result;
1275
+ },
1276
+
1277
+ showSelection: function(drawn) {
1278
+ var cm = this.cm, display = cm.display;
1279
+ removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
1280
+ removeChildrenAndAdd(display.selectionDiv, drawn.selection);
1281
+ if (drawn.teTop != null) {
1282
+ this.wrapper.style.top = drawn.teTop + "px";
1283
+ this.wrapper.style.left = drawn.teLeft + "px";
1284
+ }
1285
+ },
1286
+
1287
+ // Reset the input to correspond to the selection (or to be empty,
1288
+ // when not typing and nothing is selected)
1289
+ reset: function(typing) {
1290
+ if (this.contextMenuPending) return;
1291
+ var minimal, selected, cm = this.cm, doc = cm.doc;
1292
+ if (cm.somethingSelected()) {
1293
+ this.prevInput = "";
1294
+ var range = doc.sel.primary();
1295
+ minimal = hasCopyEvent &&
1296
+ (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
1297
+ var content = minimal ? "-" : selected || cm.getSelection();
1298
+ this.textarea.value = content;
1299
+ if (cm.state.focused) selectInput(this.textarea);
1300
+ if (ie && ie_version >= 9) this.hasSelection = content;
1301
+ } else if (!typing) {
1302
+ this.prevInput = this.textarea.value = "";
1303
+ if (ie && ie_version >= 9) this.hasSelection = null;
1304
+ }
1305
+ this.inaccurateSelection = minimal;
1306
+ },
1307
+
1308
+ getField: function() { return this.textarea; },
1309
+
1310
+ supportsTouch: function() { return false; },
1311
+
1312
+ focus: function() {
1313
+ if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
1314
+ try { this.textarea.focus(); }
1315
+ catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
1316
+ }
1317
+ },
1318
+
1319
+ blur: function() { this.textarea.blur(); },
1320
+
1321
+ resetPosition: function() {
1322
+ this.wrapper.style.top = this.wrapper.style.left = 0;
1323
+ },
1324
+
1325
+ receivedFocus: function() { this.slowPoll(); },
1326
+
1327
+ // Poll for input changes, using the normal rate of polling. This
1328
+ // runs as long as the editor is focused.
1329
+ slowPoll: function() {
1330
+ var input = this;
1331
+ if (input.pollingFast) return;
1332
+ input.polling.set(this.cm.options.pollInterval, function() {
1333
+ input.poll();
1334
+ if (input.cm.state.focused) input.slowPoll();
1335
+ });
1336
+ },
1337
+
1338
+ // When an event has just come in that is likely to add or change
1339
+ // something in the input textarea, we poll faster, to ensure that
1340
+ // the change appears on the screen quickly.
1341
+ fastPoll: function() {
1342
+ var missed = false, input = this;
1343
+ input.pollingFast = true;
1344
+ function p() {
1345
+ var changed = input.poll();
1346
+ if (!changed && !missed) {missed = true; input.polling.set(60, p);}
1347
+ else {input.pollingFast = false; input.slowPoll();}
1348
+ }
1349
+ input.polling.set(20, p);
1350
+ },
1351
+
1352
+ // Read input from the textarea, and update the document to match.
1353
+ // When something is selected, it is present in the textarea, and
1354
+ // selected (unless it is huge, in which case a placeholder is
1355
+ // used). When nothing is selected, the cursor sits after previously
1356
+ // seen text (can be empty), which is stored in prevInput (we must
1357
+ // not reset the textarea when typing, because that breaks IME).
1358
+ poll: function() {
1359
+ var cm = this.cm, input = this.textarea, prevInput = this.prevInput;
1360
+ // Since this is called a *lot*, try to bail out as cheaply as
1361
+ // possible when it is clear that nothing happened. hasSelection
1362
+ // will be the case when there is a lot of text in the textarea,
1363
+ // in which case reading its value would be expensive.
1364
+ if (!cm.state.focused || (hasSelection(input) && !prevInput) ||
1365
+ isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
1366
+ return false;
1367
+ // See paste handler for more on the fakedLastChar kludge
1368
+ if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
1369
+ input.value = input.value.substring(0, input.value.length - 1);
1370
+ cm.state.fakedLastChar = false;
1371
+ }
1372
+ var text = input.value;
1373
+ // If nothing changed, bail.
1374
+ if (text == prevInput && !cm.somethingSelected()) return false;
1375
+ // Work around nonsensical selection resetting in IE9/10, and
1376
+ // inexplicable appearance of private area unicode characters on
1377
+ // some key combos in Mac (#2689).
1378
+ if (ie && ie_version >= 9 && this.hasSelection === text ||
1379
+ mac && /[\uf700-\uf7ff]/.test(text)) {
1380
+ cm.display.input.reset();
1381
+ return false;
1382
+ }
1383
+
1384
+ if (text.charCodeAt(0) == 0x200b && cm.doc.sel == cm.display.selForContextMenu && !prevInput)
1385
+ prevInput = "\u200b";
1386
+ // Find the part of the input that is actually new
1387
+ var same = 0, l = Math.min(prevInput.length, text.length);
1388
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1389
+
1390
+ var self = this;
1391
+ runInOp(cm, function() {
1392
+ applyTextInput(cm, text.slice(same), prevInput.length - same);
1393
+
1394
+ // Don't leave long text in the textarea, since it makes further polling slow
1395
+ if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = "";
1396
+ else self.prevInput = text;
1397
+ });
1398
+ return true;
1399
+ },
1400
+
1401
+ ensurePolled: function() {
1402
+ if (this.pollingFast && this.poll()) this.pollingFast = false;
1403
+ },
1404
+
1405
+ onKeyPress: function() {
1406
+ if (ie && ie_version >= 9) this.hasSelection = null;
1407
+ this.fastPoll();
1408
+ },
1409
+
1410
+ onContextMenu: function(e) {
1411
+ var input = this, cm = input.cm, display = cm.display, te = input.textarea;
1412
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
1413
+ if (!pos || presto) return; // Opera is difficult.
1414
+
1415
+ // Reset the current text selection only if the click is done outside of the selection
1416
+ // and 'resetSelectionOnContextMenu' option is true.
1417
+ var reset = cm.options.resetSelectionOnContextMenu;
1418
+ if (reset && cm.doc.sel.contains(pos) == -1)
1419
+ operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
1420
+
1421
+ var oldCSS = te.style.cssText;
1422
+ input.wrapper.style.position = "absolute";
1423
+ te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1424
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
1425
+ (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
1426
+ "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1427
+ if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
1428
+ display.input.focus();
1429
+ if (webkit) window.scrollTo(null, oldScrollY);
1430
+ display.input.reset();
1431
+ // Adds "Select all" to context menu in FF
1432
+ if (!cm.somethingSelected()) te.value = input.prevInput = " ";
1433
+ input.contextMenuPending = true;
1434
+ display.selForContextMenu = cm.doc.sel;
1435
+ clearTimeout(display.detectingSelectAll);
1436
+
1437
+ // Select-all will be greyed out if there's nothing to select, so
1438
+ // this adds a zero-width space so that we can later check whether
1439
+ // it got selected.
1440
+ function prepareSelectAllHack() {
1441
+ if (te.selectionStart != null) {
1442
+ var selected = cm.somethingSelected();
1443
+ var extval = te.value = "\u200b" + (selected ? te.value : "");
1444
+ input.prevInput = selected ? "" : "\u200b";
1445
+ te.selectionStart = 1; te.selectionEnd = extval.length;
1446
+ // Re-set this, in case some other handler touched the
1447
+ // selection in the meantime.
1448
+ display.selForContextMenu = cm.doc.sel;
1449
+ }
1450
+ }
1451
+ function rehide() {
1452
+ input.contextMenuPending = false;
1453
+ input.wrapper.style.position = "relative";
1454
+ te.style.cssText = oldCSS;
1455
+ if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos);
1456
+
1457
+ // Try to detect the user choosing select-all
1458
+ if (te.selectionStart != null) {
1459
+ if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
1460
+ var i = 0, poll = function() {
1461
+ if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0)
1462
+ operation(cm, commands.selectAll)(cm);
1463
+ else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
1464
+ else display.input.reset();
1465
+ };
1466
+ display.detectingSelectAll = setTimeout(poll, 200);
1467
+ }
1468
+ }
1469
+
1470
+ if (ie && ie_version >= 9) prepareSelectAllHack();
1471
+ if (captureRightClick) {
1472
+ e_stop(e);
1473
+ var mouseup = function() {
1474
+ off(window, "mouseup", mouseup);
1475
+ setTimeout(rehide, 20);
1476
+ };
1477
+ on(window, "mouseup", mouseup);
1478
+ } else {
1479
+ setTimeout(rehide, 50);
1480
+ }
1481
+ },
1482
+
1483
+ setUneditable: nothing,
1484
+
1485
+ needsContentAttribute: false
1486
+ }, TextareaInput.prototype);
1487
+
1488
+ // CONTENTEDITABLE INPUT STYLE
1489
+
1490
+ function ContentEditableInput(cm) {
1491
+ this.cm = cm;
1492
+ this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
1493
+ this.polling = new Delayed();
1494
+ }
1495
+
1496
+ ContentEditableInput.prototype = copyObj({
1497
+ init: function(display) {
1498
+ var input = this, cm = input.cm;
1499
+ var div = input.div = display.lineDiv;
1500
+ div.contentEditable = "true";
1501
+ disableBrowserMagic(div);
1502
+
1503
+ on(div, "paste", function(e) {
1504
+ var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
1505
+ if (pasted) {
1506
+ e.preventDefault();
1507
+ cm.replaceSelection(pasted, null, "paste");
1508
+ }
1509
+ });
1510
+
1511
+ on(div, "compositionstart", function(e) {
1512
+ var data = e.data;
1513
+ input.composing = {sel: cm.doc.sel, data: data, startData: data};
1514
+ if (!data) return;
1515
+ var prim = cm.doc.sel.primary();
1516
+ var line = cm.getLine(prim.head.line);
1517
+ var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length));
1518
+ if (found > -1 && found <= prim.head.ch)
1519
+ input.composing.sel = simpleSelection(Pos(prim.head.line, found),
1520
+ Pos(prim.head.line, found + data.length));
1521
+ });
1522
+ on(div, "compositionupdate", function(e) {
1523
+ input.composing.data = e.data;
1524
+ });
1525
+ on(div, "compositionend", function(e) {
1526
+ var ours = input.composing;
1527
+ if (!ours) return;
1528
+ if (e.data != ours.startData && !/\u200b/.test(e.data))
1529
+ ours.data = e.data;
1530
+ // Need a small delay to prevent other code (input event,
1531
+ // selection polling) from doing damage when fired right after
1532
+ // compositionend.
1533
+ setTimeout(function() {
1534
+ if (!ours.handled)
1535
+ input.applyComposition(ours);
1536
+ if (input.composing == ours)
1537
+ input.composing = null;
1538
+ }, 50);
1539
+ });
1540
+
1541
+ on(div, "touchstart", function() {
1542
+ input.forceCompositionEnd();
1543
+ });
1544
+
1545
+ on(div, "input", function() {
1546
+ if (input.composing) return;
1547
+ if (!input.pollContent())
1548
+ runInOp(input.cm, function() {regChange(cm);});
1549
+ });
1550
+
1551
+ function onCopyCut(e) {
1552
+ if (cm.somethingSelected()) {
1553
+ lastCopied = cm.getSelections();
1554
+ if (e.type == "cut") cm.replaceSelection("", null, "cut");
1555
+ } else {
1556
+ var ranges = copyableRanges(cm);
1557
+ lastCopied = ranges.text;
1558
+ if (e.type == "cut") {
1559
+ cm.operation(function() {
1560
+ cm.setSelections(ranges.ranges, 0, sel_dontScroll);
1561
+ cm.replaceSelection("", null, "cut");
1562
+ });
1563
+ }
1564
+ }
1565
+ // iOS exposes the clipboard API, but seems to discard content inserted into it
1566
+ if (e.clipboardData && !ios) {
1567
+ e.preventDefault();
1568
+ e.clipboardData.clearData();
1569
+ e.clipboardData.setData("text/plain", lastCopied.join("\n"));
1570
+ } else {
1571
+ // Old-fashioned briefly-focus-a-textarea hack
1572
+ var kludge = hiddenTextarea(), te = kludge.firstChild;
1573
+ cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
1574
+ te.value = lastCopied.join("\n");
1575
+ var hadFocus = document.activeElement;
1576
+ selectInput(te);
1577
+ setTimeout(function() {
1578
+ cm.display.lineSpace.removeChild(kludge);
1579
+ hadFocus.focus();
1580
+ }, 50);
1581
+ }
1582
+ }
1583
+ on(div, "copy", onCopyCut);
1584
+ on(div, "cut", onCopyCut);
1585
+ },
1586
+
1587
+ prepareSelection: function() {
1588
+ var result = prepareSelection(this.cm, false);
1589
+ result.focus = this.cm.state.focused;
1590
+ return result;
1591
+ },
1592
+
1593
+ showSelection: function(info) {
1594
+ if (!info || !this.cm.display.view.length) return;
1595
+ if (info.focus) this.showPrimarySelection();
1596
+ this.showMultipleSelections(info);
1597
+ },
1598
+
1599
+ showPrimarySelection: function() {
1600
+ var sel = window.getSelection(), prim = this.cm.doc.sel.primary();
1601
+ var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset);
1602
+ var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset);
1603
+ if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
1604
+ cmp(minPos(curAnchor, curFocus), prim.from()) == 0 &&
1605
+ cmp(maxPos(curAnchor, curFocus), prim.to()) == 0)
1606
+ return;
1607
+
1608
+ var start = posToDOM(this.cm, prim.from());
1609
+ var end = posToDOM(this.cm, prim.to());
1610
+ if (!start && !end) return;
1611
+
1612
+ var view = this.cm.display.view;
1613
+ var old = sel.rangeCount && sel.getRangeAt(0);
1614
+ if (!start) {
1615
+ start = {node: view[0].measure.map[2], offset: 0};
1616
+ } else if (!end) { // FIXME dangerously hacky
1617
+ var measure = view[view.length - 1].measure;
1618
+ var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;
1619
+ end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]};
1620
+ }
1621
+
1622
+ try { var rng = range(start.node, start.offset, end.offset, end.node); }
1623
+ catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
1624
+ if (rng) {
1625
+ sel.removeAllRanges();
1626
+ sel.addRange(rng);
1627
+ if (old && sel.anchorNode == null) sel.addRange(old);
1628
+ }
1629
+ this.rememberSelection();
1630
+ },
1631
+
1632
+ showMultipleSelections: function(info) {
1633
+ removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
1634
+ removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
1635
+ },
1636
+
1637
+ rememberSelection: function() {
1638
+ var sel = window.getSelection();
1639
+ this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
1640
+ this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
1641
+ },
1642
+
1643
+ selectionInEditor: function() {
1644
+ var sel = window.getSelection();
1645
+ if (!sel.rangeCount) return false;
1646
+ var node = sel.getRangeAt(0).commonAncestorContainer;
1647
+ return contains(this.div, node);
1648
+ },
1649
+
1650
+ focus: function() {
1651
+ if (this.cm.options.readOnly != "nocursor") this.div.focus();
1652
+ },
1653
+ blur: function() { this.div.blur(); },
1654
+ getField: function() { return this.div; },
1655
+
1656
+ supportsTouch: function() { return true; },
1657
+
1658
+ receivedFocus: function() {
1659
+ var input = this;
1660
+ if (this.selectionInEditor())
1661
+ this.pollSelection();
1662
+ else
1663
+ runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; });
1664
+
1665
+ function poll() {
1666
+ if (input.cm.state.focused) {
1667
+ input.pollSelection();
1668
+ input.polling.set(input.cm.options.pollInterval, poll);
1669
+ }
1670
+ }
1671
+ this.polling.set(this.cm.options.pollInterval, poll);
1672
+ },
1673
+
1674
+ pollSelection: function() {
1675
+ if (this.composing) return;
1676
+
1677
+ var sel = window.getSelection(), cm = this.cm;
1678
+ if (sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
1679
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset) {
1680
+ this.rememberSelection();
1681
+ var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
1682
+ var head = domToPos(cm, sel.focusNode, sel.focusOffset);
1683
+ if (anchor && head) runInOp(cm, function() {
1684
+ setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);
1685
+ if (anchor.bad || head.bad) cm.curOp.selectionChanged = true;
1686
+ });
1687
+ }
1688
+ },
1689
+
1690
+ pollContent: function() {
1691
+ var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
1692
+ var from = sel.from(), to = sel.to();
1693
+ if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false;
1694
+
1695
+ var fromIndex;
1696
+ if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
1697
+ var fromLine = lineNo(display.view[0].line);
1698
+ var fromNode = display.view[0].node;
1699
+ } else {
1700
+ var fromLine = lineNo(display.view[fromIndex].line);
1701
+ var fromNode = display.view[fromIndex - 1].node.nextSibling;
1702
+ }
1703
+ var toIndex = findViewIndex(cm, to.line);
1704
+ if (toIndex == display.view.length - 1) {
1705
+ var toLine = display.viewTo - 1;
1706
+ var toNode = display.view[toIndex].node;
1707
+ } else {
1708
+ var toLine = lineNo(display.view[toIndex + 1].line) - 1;
1709
+ var toNode = display.view[toIndex + 1].node.previousSibling;
1710
+ }
1711
+
1712
+ var newText = splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
1713
+ var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));
1714
+ while (newText.length > 1 && oldText.length > 1) {
1715
+ if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }
1716
+ else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }
1717
+ else break;
1718
+ }
1719
+
1720
+ var cutFront = 0, cutEnd = 0;
1721
+ var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);
1722
+ while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
1723
+ ++cutFront;
1724
+ var newBot = lst(newText), oldBot = lst(oldText);
1725
+ var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
1726
+ oldBot.length - (oldText.length == 1 ? cutFront : 0));
1727
+ while (cutEnd < maxCutEnd &&
1728
+ newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
1729
+ ++cutEnd;
1730
+
1731
+ newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd);
1732
+ newText[0] = newText[0].slice(cutFront);
1733
+
1734
+ var chFrom = Pos(fromLine, cutFront);
1735
+ var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);
1736
+ if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
1737
+ replaceRange(cm.doc, newText, chFrom, chTo, "+input");
1738
+ return true;
1739
+ }
1740
+ },
1741
+
1742
+ ensurePolled: function() {
1743
+ this.forceCompositionEnd();
1744
+ },
1745
+ reset: function() {
1746
+ this.forceCompositionEnd();
1747
+ },
1748
+ forceCompositionEnd: function() {
1749
+ if (!this.composing || this.composing.handled) return;
1750
+ this.applyComposition(this.composing);
1751
+ this.composing.handled = true;
1752
+ this.div.blur();
1753
+ this.div.focus();
1754
+ },
1755
+ applyComposition: function(composing) {
1756
+ if (composing.data && composing.data != composing.startData)
1757
+ operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
1758
+ },
1759
+
1760
+ setUneditable: function(node) {
1761
+ node.setAttribute("contenteditable", "false");
1762
+ },
1763
+
1764
+ onKeyPress: function(e) {
1765
+ e.preventDefault();
1766
+ operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
1767
+ },
1768
+
1769
+ onContextMenu: nothing,
1770
+ resetPosition: nothing,
1771
+
1772
+ needsContentAttribute: true
1773
+ }, ContentEditableInput.prototype);
1774
+
1775
+ function posToDOM(cm, pos) {
1776
+ var view = findViewForLine(cm, pos.line);
1777
+ if (!view || view.hidden) return null;
1778
+ var line = getLine(cm.doc, pos.line);
1779
+ var info = mapFromLineView(view, line, pos.line);
1780
+
1781
+ var order = getOrder(line), side = "left";
1782
+ if (order) {
1783
+ var partPos = getBidiPartAt(order, pos.ch);
1784
+ side = partPos % 2 ? "right" : "left";
1785
+ }
1786
+ var result = nodeAndOffsetInLineMap(info.map, pos.ch, "left");
1787
+ result.offset = result.collapse == "right" ? result.end : result.start;
1788
+ return result;
1789
+ }
1790
+
1791
+ function badPos(pos, bad) { if (bad) pos.bad = true; return pos; }
1792
+
1793
+ function domToPos(cm, node, offset) {
1794
+ var lineNode;
1795
+ if (node == cm.display.lineDiv) {
1796
+ lineNode = cm.display.lineDiv.childNodes[offset];
1797
+ if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true);
1798
+ node = null; offset = 0;
1799
+ } else {
1800
+ for (lineNode = node;; lineNode = lineNode.parentNode) {
1801
+ if (!lineNode || lineNode == cm.display.lineDiv) return null;
1802
+ if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break;
1803
+ }
1804
+ }
1805
+ for (var i = 0; i < cm.display.view.length; i++) {
1806
+ var lineView = cm.display.view[i];
1807
+ if (lineView.node == lineNode)
1808
+ return locateNodeInLineView(lineView, node, offset);
1809
+ }
1810
+ }
1811
+
1812
+ function locateNodeInLineView(lineView, node, offset) {
1813
+ var wrapper = lineView.text.firstChild, bad = false;
1814
+ if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true);
1815
+ if (node == wrapper) {
1816
+ bad = true;
1817
+ node = wrapper.childNodes[offset];
1818
+ offset = 0;
1819
+ if (!node) {
1820
+ var line = lineView.rest ? lst(lineView.rest) : lineView.line;
1821
+ return badPos(Pos(lineNo(line), line.text.length), bad);
1822
+ }
1823
+ }
1824
+
1825
+ var textNode = node.nodeType == 3 ? node : null, topNode = node;
1826
+ if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
1827
+ textNode = node.firstChild;
1828
+ if (offset) offset = textNode.nodeValue.length;
1829
+ }
1830
+ while (topNode.parentNode != wrapper) topNode = topNode.parentNode;
1831
+ var measure = lineView.measure, maps = measure.maps;
1832
+
1833
+ function find(textNode, topNode, offset) {
1834
+ for (var i = -1; i < (maps ? maps.length : 0); i++) {
1835
+ var map = i < 0 ? measure.map : maps[i];
1836
+ for (var j = 0; j < map.length; j += 3) {
1837
+ var curNode = map[j + 2];
1838
+ if (curNode == textNode || curNode == topNode) {
1839
+ var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
1840
+ var ch = map[j] + offset;
1841
+ if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)];
1842
+ return Pos(line, ch);
1843
+ }
1844
+ }
1845
+ }
1846
+ }
1847
+ var found = find(textNode, topNode, offset);
1848
+ if (found) return badPos(found, bad);
1849
+
1850
+ // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
1851
+ for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
1852
+ found = find(after, after.firstChild, 0);
1853
+ if (found)
1854
+ return badPos(Pos(found.line, found.ch - dist), bad);
1855
+ else
1856
+ dist += after.textContent.length;
1857
+ }
1858
+ for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) {
1859
+ found = find(before, before.firstChild, -1);
1860
+ if (found)
1861
+ return badPos(Pos(found.line, found.ch + dist), bad);
1862
+ else
1863
+ dist += after.textContent.length;
1864
+ }
1865
+ }
1866
+
1867
+ function domTextBetween(cm, from, to, fromLine, toLine) {
1868
+ var text = "", closing = false;
1869
+ function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
1870
+ function walk(node) {
1871
+ if (node.nodeType == 1) {
1872
+ var cmText = node.getAttribute("cm-text");
1873
+ if (cmText != null) {
1874
+ if (cmText == "") cmText = node.textContent.replace(/\u200b/g, "");
1875
+ text += cmText;
1876
+ return;
1877
+ }
1878
+ var markerID = node.getAttribute("cm-marker"), range;
1879
+ if (markerID) {
1880
+ var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));
1881
+ if (found.length && (range = found[0].find()))
1882
+ text += getBetween(cm.doc, range.from, range.to).join("\n");
1883
+ return;
1884
+ }
1885
+ if (node.getAttribute("contenteditable") == "false") return;
1886
+ for (var i = 0; i < node.childNodes.length; i++)
1887
+ walk(node.childNodes[i]);
1888
+ if (/^(pre|div|p)$/i.test(node.nodeName))
1889
+ closing = true;
1890
+ } else if (node.nodeType == 3) {
1891
+ var val = node.nodeValue;
1892
+ if (!val) return;
1893
+ if (closing) {
1894
+ text += "\n";
1895
+ closing = false;
1896
+ }
1897
+ text += val;
1898
+ }
1899
+ }
1900
+ for (;;) {
1901
+ walk(from);
1902
+ if (from == to) break;
1903
+ from = from.nextSibling;
1904
+ }
1905
+ return text;
1906
+ }
1907
+
1908
+ CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
1909
+
1087
1910
  // SELECTION / CURSOR
1088
1911
 
1089
1912
  // Selection objects are immutable. A new one is created every time
@@ -1371,13 +2194,17 @@
1371
2194
 
1372
2195
  // SELECTION DRAWING
1373
2196
 
1374
- // Redraw the selection and/or cursor
1375
- function drawSelection(cm) {
1376
- var display = cm.display, doc = cm.doc, result = {};
2197
+ function updateSelection(cm) {
2198
+ cm.display.input.showSelection(cm.display.input.prepareSelection());
2199
+ }
2200
+
2201
+ function prepareSelection(cm, primary) {
2202
+ var doc = cm.doc, result = {};
1377
2203
  var curFragment = result.cursors = document.createDocumentFragment();
1378
2204
  var selFragment = result.selection = document.createDocumentFragment();
1379
2205
 
1380
2206
  for (var i = 0; i < doc.sel.ranges.length; i++) {
2207
+ if (primary === false && i == doc.sel.primIndex) continue;
1381
2208
  var range = doc.sel.ranges[i];
1382
2209
  var collapsed = range.empty();
1383
2210
  if (collapsed || cm.options.showCursorWhenSelecting)
@@ -1385,33 +2212,9 @@
1385
2212
  if (!collapsed)
1386
2213
  drawSelectionRange(cm, range, selFragment);
1387
2214
  }
1388
-
1389
- // Move the hidden textarea near the cursor to prevent scrolling artifacts
1390
- if (cm.options.moveInputWithCursor) {
1391
- var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
1392
- var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
1393
- result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
1394
- headPos.top + lineOff.top - wrapOff.top));
1395
- result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
1396
- headPos.left + lineOff.left - wrapOff.left));
1397
- }
1398
-
1399
2215
  return result;
1400
2216
  }
1401
2217
 
1402
- function showSelection(cm, drawn) {
1403
- removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
1404
- removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
1405
- if (drawn.teTop != null) {
1406
- cm.display.inputDiv.style.top = drawn.teTop + "px";
1407
- cm.display.inputDiv.style.left = drawn.teLeft + "px";
1408
- }
1409
- }
1410
-
1411
- function updateSelection(cm) {
1412
- showSelection(cm, drawSelection(cm));
1413
- }
1414
-
1415
2218
  // Draws a cursor for the given range
1416
2219
  function drawSelectionCursor(cm, range, output) {
1417
2220
  var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
@@ -1734,9 +2537,7 @@
1734
2537
 
1735
2538
  var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
1736
2539
 
1737
- function measureCharInner(cm, prepared, ch, bias) {
1738
- var map = prepared.map;
1739
-
2540
+ function nodeAndOffsetInLineMap(map, ch, bias) {
1740
2541
  var node, start, end, collapse;
1741
2542
  // First, search the line map for the text node corresponding to,
1742
2543
  // or closest to, the target character.
@@ -1770,13 +2571,19 @@
1770
2571
  break;
1771
2572
  }
1772
2573
  }
2574
+ return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd};
2575
+ }
2576
+
2577
+ function measureCharInner(cm, prepared, ch, bias) {
2578
+ var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
2579
+ var node = place.node, start = place.start, end = place.end, collapse = place.collapse;
1773
2580
 
1774
2581
  var rect;
1775
2582
  if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
1776
2583
  for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
1777
- while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
1778
- while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
1779
- if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
2584
+ while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start;
2585
+ while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end;
2586
+ if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) {
1780
2587
  rect = node.parentNode.getBoundingClientRect();
1781
2588
  } else if (ie && cm.options.lineWrapping) {
1782
2589
  var rects = range(node, start, end).getClientRects();
@@ -2181,7 +2988,7 @@
2181
2988
  }
2182
2989
 
2183
2990
  if (op.updatedDisplay || op.selectionChanged)
2184
- op.newSelectionNodes = drawSelection(cm);
2991
+ op.preparedSelection = display.input.prepareSelection();
2185
2992
  }
2186
2993
 
2187
2994
  function endOperation_W2(op) {
@@ -2194,8 +3001,8 @@
2194
3001
  cm.display.maxLineChanged = false;
2195
3002
  }
2196
3003
 
2197
- if (op.newSelectionNodes)
2198
- showSelection(cm, op.newSelectionNodes);
3004
+ if (op.preparedSelection)
3005
+ cm.display.input.showSelection(op.preparedSelection);
2199
3006
  if (op.updatedDisplay)
2200
3007
  setDocumentHeight(cm, op.barMeasure);
2201
3008
  if (op.updatedDisplay || op.startHeight != cm.doc.height)
@@ -2204,7 +3011,7 @@
2204
3011
  if (op.selectionChanged) restartBlink(cm);
2205
3012
 
2206
3013
  if (cm.state.focused && op.updateInput)
2207
- resetInput(cm, op.typing);
3014
+ cm.display.input.reset(op.typing);
2208
3015
  }
2209
3016
 
2210
3017
  function endOperation_finish(op) {
@@ -2476,169 +3283,6 @@
2476
3283
  return dirty;
2477
3284
  }
2478
3285
 
2479
- // INPUT HANDLING
2480
-
2481
- // Poll for input changes, using the normal rate of polling. This
2482
- // runs as long as the editor is focused.
2483
- function slowPoll(cm) {
2484
- if (cm.display.pollingFast) return;
2485
- cm.display.poll.set(cm.options.pollInterval, function() {
2486
- readInput(cm);
2487
- if (cm.state.focused) slowPoll(cm);
2488
- });
2489
- }
2490
-
2491
- // When an event has just come in that is likely to add or change
2492
- // something in the input textarea, we poll faster, to ensure that
2493
- // the change appears on the screen quickly.
2494
- function fastPoll(cm) {
2495
- var missed = false;
2496
- cm.display.pollingFast = true;
2497
- function p() {
2498
- var changed = readInput(cm);
2499
- if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
2500
- else {cm.display.pollingFast = false; slowPoll(cm);}
2501
- }
2502
- cm.display.poll.set(20, p);
2503
- }
2504
-
2505
- // This will be set to an array of strings when copying, so that,
2506
- // when pasting, we know what kind of selections the copied text
2507
- // was made out of.
2508
- var lastCopied = null;
2509
-
2510
- // Read input from the textarea, and update the document to match.
2511
- // When something is selected, it is present in the textarea, and
2512
- // selected (unless it is huge, in which case a placeholder is
2513
- // used). When nothing is selected, the cursor sits after previously
2514
- // seen text (can be empty), which is stored in prevInput (we must
2515
- // not reset the textarea when typing, because that breaks IME).
2516
- function readInput(cm) {
2517
- var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
2518
- // Since this is called a *lot*, try to bail out as cheaply as
2519
- // possible when it is clear that nothing happened. hasSelection
2520
- // will be the case when there is a lot of text in the textarea,
2521
- // in which case reading its value would be expensive.
2522
- if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
2523
- return false;
2524
- // See paste handler for more on the fakedLastChar kludge
2525
- if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
2526
- input.value = input.value.substring(0, input.value.length - 1);
2527
- cm.state.fakedLastChar = false;
2528
- }
2529
- var text = input.value;
2530
- // If nothing changed, bail.
2531
- if (text == prevInput && !cm.somethingSelected()) return false;
2532
- // Work around nonsensical selection resetting in IE9/10, and
2533
- // inexplicable appearance of private area unicode characters on
2534
- // some key combos in Mac (#2689).
2535
- if (ie && ie_version >= 9 && cm.display.inputHasSelection === text ||
2536
- mac && /[\uf700-\uf7ff]/.test(text)) {
2537
- resetInput(cm);
2538
- return false;
2539
- }
2540
-
2541
- var withOp = !cm.curOp;
2542
- if (withOp) startOperation(cm);
2543
- cm.display.shift = false;
2544
-
2545
- if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
2546
- prevInput = "\u200b";
2547
- // Find the part of the input that is actually new
2548
- var same = 0, l = Math.min(prevInput.length, text.length);
2549
- while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
2550
- var inserted = text.slice(same), textLines = splitLines(inserted);
2551
-
2552
- // When pasing N lines into N selections, insert one line per selection
2553
- var multiPaste = null;
2554
- if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
2555
- if (lastCopied && lastCopied.join("\n") == inserted)
2556
- multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
2557
- else if (textLines.length == doc.sel.ranges.length)
2558
- multiPaste = map(textLines, function(l) { return [l]; });
2559
- }
2560
-
2561
- // Normal behavior is to insert the new text into every selection
2562
- for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
2563
- var range = doc.sel.ranges[i];
2564
- var from = range.from(), to = range.to();
2565
- // Handle deletion
2566
- if (same < prevInput.length)
2567
- from = Pos(from.line, from.ch - (prevInput.length - same));
2568
- // Handle overwrite
2569
- else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
2570
- to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
2571
- var updateInput = cm.curOp.updateInput;
2572
- var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
2573
- origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
2574
- makeChange(cm.doc, changeEvent);
2575
- signalLater(cm, "inputRead", cm, changeEvent);
2576
- // When an 'electric' character is inserted, immediately trigger a reindent
2577
- if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
2578
- cm.options.smartIndent && range.head.ch < 100 &&
2579
- (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
2580
- var mode = cm.getModeAt(range.head);
2581
- var end = changeEnd(changeEvent);
2582
- if (mode.electricChars) {
2583
- for (var j = 0; j < mode.electricChars.length; j++)
2584
- if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
2585
- indentLine(cm, end.line, "smart");
2586
- break;
2587
- }
2588
- } else if (mode.electricInput) {
2589
- if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
2590
- indentLine(cm, end.line, "smart");
2591
- }
2592
- }
2593
- }
2594
- ensureCursorVisible(cm);
2595
- cm.curOp.updateInput = updateInput;
2596
- cm.curOp.typing = true;
2597
-
2598
- // Don't leave long text in the textarea, since it makes further polling slow
2599
- if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
2600
- else cm.display.prevInput = text;
2601
- if (withOp) endOperation(cm);
2602
- cm.state.pasteIncoming = cm.state.cutIncoming = false;
2603
- return true;
2604
- }
2605
-
2606
- // Reset the input to correspond to the selection (or to be empty,
2607
- // when not typing and nothing is selected)
2608
- function resetInput(cm, typing) {
2609
- if (cm.display.contextMenuPending) return;
2610
- var minimal, selected, doc = cm.doc;
2611
- if (cm.somethingSelected()) {
2612
- cm.display.prevInput = "";
2613
- var range = doc.sel.primary();
2614
- minimal = hasCopyEvent &&
2615
- (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
2616
- var content = minimal ? "-" : selected || cm.getSelection();
2617
- cm.display.input.value = content;
2618
- if (cm.state.focused) selectInput(cm.display.input);
2619
- if (ie && ie_version >= 9) cm.display.inputHasSelection = content;
2620
- } else if (!typing) {
2621
- cm.display.prevInput = cm.display.input.value = "";
2622
- if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
2623
- }
2624
- cm.display.inaccurateSelection = minimal;
2625
- }
2626
-
2627
- function focusInput(cm) {
2628
- if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input)) {
2629
- try { cm.display.input.focus(); }
2630
- catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
2631
- }
2632
- }
2633
-
2634
- function ensureFocus(cm) {
2635
- if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
2636
- }
2637
-
2638
- function isReadOnly(cm) {
2639
- return cm.options.readOnly || cm.doc.cantEdit;
2640
- }
2641
-
2642
3286
  // EVENT HANDLERS
2643
3287
 
2644
3288
  // Attach the necessary event handlers when initializing the editor
@@ -2657,15 +3301,64 @@
2657
3301
  }));
2658
3302
  else
2659
3303
  on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
2660
- // Prevent normal selection in the editor (we handle our own)
2661
- on(d.lineSpace, "selectstart", function(e) {
2662
- if (!eventInWidget(d, e)) e_preventDefault(e);
2663
- });
2664
3304
  // Some browsers fire contextmenu *after* opening the menu, at
2665
3305
  // which point we can't mess with it anymore. Context menu is
2666
3306
  // handled in onMouseDown for these browsers.
2667
3307
  if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
2668
3308
 
3309
+ // Used to suppress mouse event handling when a touch happens
3310
+ var touchFinished, prevTouch = {end: 0};
3311
+ function finishTouch() {
3312
+ if (d.activeTouch) {
3313
+ touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000);
3314
+ prevTouch = d.activeTouch;
3315
+ prevTouch.end = +new Date;
3316
+ }
3317
+ };
3318
+ function isMouseLikeTouchEvent(e) {
3319
+ if (e.touches.length != 1) return false;
3320
+ var touch = e.touches[0];
3321
+ return touch.radiusX <= 1 && touch.radiusY <= 1;
3322
+ }
3323
+ function farAway(touch, other) {
3324
+ if (other.left == null) return true;
3325
+ var dx = other.left - touch.left, dy = other.top - touch.top;
3326
+ return dx * dx + dy * dy > 20 * 20;
3327
+ }
3328
+ on(d.scroller, "touchstart", function(e) {
3329
+ if (!isMouseLikeTouchEvent(e)) {
3330
+ clearTimeout(touchFinished);
3331
+ var now = +new Date;
3332
+ d.activeTouch = {start: now, moved: false,
3333
+ prev: now - prevTouch.end <= 300 ? prevTouch : null};
3334
+ if (e.touches.length == 1) {
3335
+ d.activeTouch.left = e.touches[0].pageX;
3336
+ d.activeTouch.top = e.touches[0].pageY;
3337
+ }
3338
+ }
3339
+ });
3340
+ on(d.scroller, "touchmove", function() {
3341
+ if (d.activeTouch) d.activeTouch.moved = true;
3342
+ });
3343
+ on(d.scroller, "touchend", function(e) {
3344
+ var touch = d.activeTouch;
3345
+ if (touch && !eventInWidget(d, e) && touch.left != null &&
3346
+ !touch.moved && new Date - touch.start < 300) {
3347
+ var pos = cm.coordsChar(d.activeTouch, "page"), range;
3348
+ if (!touch.prev || farAway(touch, touch.prev)) // Single tap
3349
+ range = new Range(pos, pos);
3350
+ else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
3351
+ range = cm.findWordAt(pos);
3352
+ else // Triple tap
3353
+ range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0)));
3354
+ cm.setSelection(range.anchor, range.head);
3355
+ cm.focus();
3356
+ e_preventDefault(e);
3357
+ }
3358
+ finishTouch();
3359
+ });
3360
+ on(d.scroller, "touchcancel", finishTouch);
3361
+
2669
3362
  // Sync scrolling between fake scrollbars and real scrollable
2670
3363
  // area, ensure viewport is updated when scrolling.
2671
3364
  on(d.scroller, "scroll", function() {
@@ -2683,16 +3376,6 @@
2683
3376
  // Prevent wrapper from ever scrolling
2684
3377
  on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
2685
3378
 
2686
- on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); });
2687
- on(d.input, "input", function() {
2688
- if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
2689
- readInput(cm);
2690
- });
2691
- on(d.input, "keydown", operation(cm, onKeyDown));
2692
- on(d.input, "keypress", operation(cm, onKeyPress));
2693
- on(d.input, "focus", bind(onFocus, cm));
2694
- on(d.input, "blur", bind(onBlur, cm));
2695
-
2696
3379
  function drag_(e) {
2697
3380
  if (!signalDOMEvent(cm, e)) e_stop(e);
2698
3381
  }
@@ -2702,67 +3385,13 @@
2702
3385
  on(d.scroller, "dragover", drag_);
2703
3386
  on(d.scroller, "drop", operation(cm, onDrop));
2704
3387
  }
2705
- on(d.scroller, "paste", function(e) {
2706
- if (eventInWidget(d, e)) return;
2707
- cm.state.pasteIncoming = true;
2708
- focusInput(cm);
2709
- fastPoll(cm);
2710
- });
2711
- on(d.input, "paste", function() {
2712
- // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
2713
- // Add a char to the end of textarea before paste occur so that
2714
- // selection doesn't span to the end of textarea.
2715
- if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
2716
- var start = d.input.selectionStart, end = d.input.selectionEnd;
2717
- d.input.value += "$";
2718
- // The selection end needs to be set before the start, otherwise there
2719
- // can be an intermediate non-empty selection between the two, which
2720
- // can override the middle-click paste buffer on linux and cause the
2721
- // wrong thing to get pasted.
2722
- d.input.selectionEnd = end;
2723
- d.input.selectionStart = start;
2724
- cm.state.fakedLastChar = true;
2725
- }
2726
- cm.state.pasteIncoming = true;
2727
- fastPoll(cm);
2728
- });
2729
-
2730
- function prepareCopyCut(e) {
2731
- if (cm.somethingSelected()) {
2732
- lastCopied = cm.getSelections();
2733
- if (d.inaccurateSelection) {
2734
- d.prevInput = "";
2735
- d.inaccurateSelection = false;
2736
- d.input.value = lastCopied.join("\n");
2737
- selectInput(d.input);
2738
- }
2739
- } else {
2740
- var text = [], ranges = [];
2741
- for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
2742
- var line = cm.doc.sel.ranges[i].head.line;
2743
- var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
2744
- ranges.push(lineRange);
2745
- text.push(cm.getRange(lineRange.anchor, lineRange.head));
2746
- }
2747
- if (e.type == "cut") {
2748
- cm.setSelections(ranges, null, sel_dontScroll);
2749
- } else {
2750
- d.prevInput = "";
2751
- d.input.value = text.join("\n");
2752
- selectInput(d.input);
2753
- }
2754
- lastCopied = text;
2755
- }
2756
- if (e.type == "cut") cm.state.cutIncoming = true;
2757
- }
2758
- on(d.input, "cut", prepareCopyCut);
2759
- on(d.input, "copy", prepareCopyCut);
2760
3388
 
2761
- // Needed to handle Tab key in KHTML
2762
- if (khtml) on(d.sizer, "mouseup", function() {
2763
- if (activeElt() == d.input) d.input.blur();
2764
- focusInput(cm);
2765
- });
3389
+ var inp = d.input.getField();
3390
+ on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
3391
+ on(inp, "keydown", operation(cm, onKeyDown));
3392
+ on(inp, "keypress", operation(cm, onKeyPress));
3393
+ on(inp, "focus", bind(onFocus, cm));
3394
+ on(inp, "blur", bind(onBlur, cm));
2766
3395
  }
2767
3396
 
2768
3397
  // Called when the window resizes
@@ -2794,7 +3423,7 @@
2794
3423
  // coordinates beyond the right of the text.
2795
3424
  function posFromMouse(cm, e, liberal, forRect) {
2796
3425
  var display = cm.display;
2797
- if (!liberal && e_target(e).getAttribute("not-content") == "true") return null;
3426
+ if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null;
2798
3427
 
2799
3428
  var x, y, space = display.lineSpace.getBoundingClientRect();
2800
3429
  // Fails unpredictably on IE[67] when mouse is dragged around quickly.
@@ -2814,8 +3443,8 @@
2814
3443
  // middle-click-paste. Or it might be a click on something we should
2815
3444
  // not interfere with, such as a scrollbar or widget.
2816
3445
  function onMouseDown(e) {
2817
- if (signalDOMEvent(this, e)) return;
2818
3446
  var cm = this, display = cm.display;
3447
+ if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return;
2819
3448
  display.shift = e.shiftKey;
2820
3449
 
2821
3450
  if (eventInWidget(display, e)) {
@@ -2841,7 +3470,7 @@
2841
3470
  case 2:
2842
3471
  if (webkit) cm.state.lastMiddleDown = +new Date;
2843
3472
  if (start) extendSelection(cm.doc, start);
2844
- setTimeout(bind(focusInput, cm), 20);
3473
+ setTimeout(function() {display.input.focus();}, 20);
2845
3474
  e_preventDefault(e);
2846
3475
  break;
2847
3476
  case 3:
@@ -2852,7 +3481,8 @@
2852
3481
 
2853
3482
  var lastClick, lastDoubleClick;
2854
3483
  function leftButtonDown(cm, e, start) {
2855
- setTimeout(bind(ensureFocus, cm), 0);
3484
+ if (ie) setTimeout(bind(ensureFocus, cm), 0);
3485
+ else ensureFocus(cm);
2856
3486
 
2857
3487
  var now = +new Date, type;
2858
3488
  if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
@@ -2887,10 +3517,10 @@
2887
3517
  e_preventDefault(e2);
2888
3518
  if (!modifier)
2889
3519
  extendSelection(cm.doc, start);
2890
- focusInput(cm);
3520
+ display.input.focus();
2891
3521
  // Work around unexplainable focus problem in IE9 (#2127)
2892
3522
  if (ie && ie_version == 9)
2893
- setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
3523
+ setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
2894
3524
  }
2895
3525
  });
2896
3526
  // Let the drag handler handle this.
@@ -3028,7 +3658,7 @@
3028
3658
  function done(e) {
3029
3659
  counter = Infinity;
3030
3660
  e_preventDefault(e);
3031
- focusInput(cm);
3661
+ display.input.focus();
3032
3662
  off(document, "mousemove", move);
3033
3663
  off(document, "mouseup", up);
3034
3664
  doc.history.lastSelOrigin = null;
@@ -3107,7 +3737,7 @@
3107
3737
  if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
3108
3738
  cm.state.draggingText(e);
3109
3739
  // Ensure the editor is re-focused
3110
- setTimeout(bind(focusInput, cm), 20);
3740
+ setTimeout(function() {cm.display.input.focus();}, 20);
3111
3741
  return;
3112
3742
  }
3113
3743
  try {
@@ -3119,7 +3749,7 @@
3119
3749
  if (selected) for (var i = 0; i < selected.length; ++i)
3120
3750
  replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
3121
3751
  cm.replaceSelection(text, "around", "paste");
3122
- focusInput(cm);
3752
+ cm.display.input.focus();
3123
3753
  }
3124
3754
  }
3125
3755
  catch(e){}
@@ -3286,7 +3916,7 @@
3286
3916
  }
3287
3917
  // Ensure previous input has been read, so that the handler sees a
3288
3918
  // consistent view of the document
3289
- if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
3919
+ cm.display.input.ensurePolled();
3290
3920
  var prevShift = cm.display.shift, done = false;
3291
3921
  try {
3292
3922
  if (isReadOnly(cm)) cm.state.suppressEdits = true;
@@ -3316,7 +3946,7 @@
3316
3946
  stopSeq.set(50, function() {
3317
3947
  if (cm.state.keySeq == seq) {
3318
3948
  cm.state.keySeq = null;
3319
- resetInput(cm);
3949
+ cm.display.input.reset();
3320
3950
  }
3321
3951
  });
3322
3952
  name = seq + " " + name;
@@ -3409,14 +4039,13 @@
3409
4039
 
3410
4040
  function onKeyPress(e) {
3411
4041
  var cm = this;
3412
- if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return;
4042
+ if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return;
3413
4043
  var keyCode = e.keyCode, charCode = e.charCode;
3414
4044
  if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
3415
- if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
4045
+ if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return;
3416
4046
  var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
3417
4047
  if (handleCharBinding(cm, e, ch)) return;
3418
- if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
3419
- fastPoll(cm);
4048
+ cm.display.input.onKeyPress(e);
3420
4049
  }
3421
4050
 
3422
4051
  // FOCUS/BLUR EVENTS
@@ -3427,15 +4056,15 @@
3427
4056
  signal(cm, "focus", cm);
3428
4057
  cm.state.focused = true;
3429
4058
  addClass(cm.display.wrapper, "CodeMirror-focused");
3430
- // The prevInput test prevents this from firing when a context
3431
- // menu is closed (since the resetInput would kill the
4059
+ // This test prevents this from firing when a context
4060
+ // menu is closed (since the input reset would kill the
3432
4061
  // select-all detection hack)
3433
4062
  if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
3434
- resetInput(cm);
3435
- if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
4063
+ cm.display.input.reset();
4064
+ if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730
3436
4065
  }
4066
+ cm.display.input.receivedFocus();
3437
4067
  }
3438
- slowPoll(cm);
3439
4068
  restartBlink(cm);
3440
4069
  }
3441
4070
  function onBlur(cm) {
@@ -3454,80 +4083,8 @@
3454
4083
  // textarea (making it as unobtrusive as possible) to let the
3455
4084
  // right-click take effect on it.
3456
4085
  function onContextMenu(cm, e) {
3457
- if (signalDOMEvent(cm, e, "contextmenu")) return;
3458
- var display = cm.display;
3459
- if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
3460
-
3461
- var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
3462
- if (!pos || presto) return; // Opera is difficult.
3463
-
3464
- // Reset the current text selection only if the click is done outside of the selection
3465
- // and 'resetSelectionOnContextMenu' option is true.
3466
- var reset = cm.options.resetSelectionOnContextMenu;
3467
- if (reset && cm.doc.sel.contains(pos) == -1)
3468
- operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
3469
-
3470
- var oldCSS = display.input.style.cssText;
3471
- display.inputDiv.style.position = "absolute";
3472
- display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
3473
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
3474
- (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
3475
- "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
3476
- if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
3477
- focusInput(cm);
3478
- if (webkit) window.scrollTo(null, oldScrollY);
3479
- resetInput(cm);
3480
- // Adds "Select all" to context menu in FF
3481
- if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
3482
- display.contextMenuPending = true;
3483
- display.selForContextMenu = cm.doc.sel;
3484
- clearTimeout(display.detectingSelectAll);
3485
-
3486
- // Select-all will be greyed out if there's nothing to select, so
3487
- // this adds a zero-width space so that we can later check whether
3488
- // it got selected.
3489
- function prepareSelectAllHack() {
3490
- if (display.input.selectionStart != null) {
3491
- var selected = cm.somethingSelected();
3492
- var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
3493
- display.prevInput = selected ? "" : "\u200b";
3494
- display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
3495
- // Re-set this, in case some other handler touched the
3496
- // selection in the meantime.
3497
- display.selForContextMenu = cm.doc.sel;
3498
- }
3499
- }
3500
- function rehide() {
3501
- display.contextMenuPending = false;
3502
- display.inputDiv.style.position = "relative";
3503
- display.input.style.cssText = oldCSS;
3504
- if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos);
3505
- slowPoll(cm);
3506
-
3507
- // Try to detect the user choosing select-all
3508
- if (display.input.selectionStart != null) {
3509
- if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
3510
- var i = 0, poll = function() {
3511
- if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
3512
- operation(cm, commands.selectAll)(cm);
3513
- else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
3514
- else resetInput(cm);
3515
- };
3516
- display.detectingSelectAll = setTimeout(poll, 200);
3517
- }
3518
- }
3519
-
3520
- if (ie && ie_version >= 9) prepareSelectAllHack();
3521
- if (captureRightClick) {
3522
- e_stop(e);
3523
- var mouseup = function() {
3524
- off(window, "mouseup", mouseup);
3525
- setTimeout(rehide, 20);
3526
- };
3527
- on(window, "mouseup", mouseup);
3528
- } else {
3529
- setTimeout(rehide, 50);
3530
- }
4086
+ if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return;
4087
+ cm.display.input.onContextMenu(e);
3531
4088
  }
3532
4089
 
3533
4090
  function contextMenuInGutter(cm, e) {
@@ -4156,7 +4713,7 @@
4156
4713
 
4157
4714
  CodeMirror.prototype = {
4158
4715
  constructor: CodeMirror,
4159
- focus: function(){window.focus(); focusInput(this); fastPoll(this);},
4716
+ focus: function(){window.focus(); this.display.input.focus();},
4160
4717
 
4161
4718
  setOption: function(option, value) {
4162
4719
  var options = this.options, old = options[option];
@@ -4378,6 +4935,7 @@
4378
4935
  var top = pos.bottom, left = pos.left;
4379
4936
  node.style.position = "absolute";
4380
4937
  node.setAttribute("cm-ignore-events", "true");
4938
+ this.display.input.setUneditable(node);
4381
4939
  display.sizer.appendChild(node);
4382
4940
  if (vert == "over") {
4383
4941
  top = pos.top;
@@ -4504,7 +5062,7 @@
4504
5062
 
4505
5063
  signal(this, "overwriteToggle", this, this.state.overwrite);
4506
5064
  },
4507
- hasFocus: function() { return activeElt() == this.display.input; },
5065
+ hasFocus: function() { return this.display.input.getField() == activeElt(); },
4508
5066
 
4509
5067
  scrollTo: methodOp(function(x, y) {
4510
5068
  if (x != null || y != null) resolveScrollToPos(this);
@@ -4580,14 +5138,14 @@
4580
5138
  old.cm = null;
4581
5139
  attachDoc(this, doc);
4582
5140
  clearCaches(this);
4583
- resetInput(this);
5141
+ this.display.input.reset();
4584
5142
  this.scrollTo(doc.scrollLeft, doc.scrollTop);
4585
5143
  this.curOp.forceScroll = true;
4586
5144
  signalLater(this, "swapDoc", this, old);
4587
5145
  return old;
4588
5146
  }),
4589
5147
 
4590
- getInputField: function(){return this.display.input;},
5148
+ getInputField: function(){return this.display.input.getField();},
4591
5149
  getWrapperElement: function(){return this.display.wrapper;},
4592
5150
  getScrollerElement: function(){return this.display.scroller;},
4593
5151
  getGutterElement: function(){return this.display.gutters;}
@@ -4634,6 +5192,9 @@
4634
5192
  }, true);
4635
5193
  option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
4636
5194
  option("electricChars", true);
5195
+ option("inputStyle", mobile ? "contenteditable" : "textarea", function() {
5196
+ throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME
5197
+ }, true);
4637
5198
  option("rtlMoveVisually", !windows);
4638
5199
  option("wholeLineUpdateBefore", true);
4639
5200
 
@@ -4682,10 +5243,10 @@
4682
5243
  cm.display.disabled = true;
4683
5244
  } else {
4684
5245
  cm.display.disabled = false;
4685
- if (!val) resetInput(cm);
5246
+ if (!val) cm.display.input.reset();
4686
5247
  }
4687
5248
  });
4688
- option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
5249
+ option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
4689
5250
  option("dragDrop", true);
4690
5251
 
4691
5252
  option("cursorBlinkRate", 530);
@@ -4702,11 +5263,11 @@
4702
5263
  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
4703
5264
  option("maxHighlightLength", 10000, resetModeState, true);
4704
5265
  option("moveInputWithCursor", true, function(cm, val) {
4705
- if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
5266
+ if (!val) cm.display.input.resetPosition();
4706
5267
  });
4707
5268
 
4708
5269
  option("tabindex", null, function(cm, val) {
4709
- cm.display.input.tabIndex = val || "";
5270
+ cm.display.input.getField().tabIndex = val || "";
4710
5271
  });
4711
5272
  option("autofocus", null);
4712
5273
 
@@ -5139,8 +5700,8 @@
5139
5700
  CodeMirror.fromTextArea = function(textarea, options) {
5140
5701
  options = options ? copyObj(options) : {};
5141
5702
  options.value = textarea.value;
5142
- if (!options.tabindex && textarea.tabindex)
5143
- options.tabindex = textarea.tabindex;
5703
+ if (!options.tabindex && textarea.tabIndex)
5704
+ options.tabindex = textarea.tabIndex;
5144
5705
  if (!options.placeholder && textarea.placeholder)
5145
5706
  options.placeholder = textarea.placeholder;
5146
5707
  // Set autofocus to true if this textarea is focused, or if it has
@@ -5280,10 +5841,13 @@
5280
5841
  // marker continues beyond the start/end of the line. Markers have
5281
5842
  // links back to the lines they currently touch.
5282
5843
 
5844
+ var nextMarkerId = 0;
5845
+
5283
5846
  var TextMarker = CodeMirror.TextMarker = function(doc, type) {
5284
5847
  this.lines = [];
5285
5848
  this.type = type;
5286
5849
  this.doc = doc;
5850
+ this.id = ++nextMarkerId;
5287
5851
  };
5288
5852
  eventMixin(TextMarker);
5289
5853
 
@@ -6233,9 +6797,11 @@
6233
6797
  var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
6234
6798
  var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
6235
6799
  txt.setAttribute("role", "presentation");
6800
+ txt.setAttribute("cm-text", "\t");
6236
6801
  builder.col += tabWidth;
6237
6802
  } else {
6238
6803
  var txt = builder.cm.options.specialCharPlaceholder(m[0]);
6804
+ txt.setAttribute("cm-text", m[0]);
6239
6805
  if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
6240
6806
  else content.appendChild(txt);
6241
6807
  builder.col += 1;
@@ -6290,8 +6856,14 @@
6290
6856
 
6291
6857
  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
6292
6858
  var widget = !ignoreWidget && marker.widgetNode;
6859
+ if (widget) builder.map.push(builder.pos, builder.pos + size, widget);
6860
+ if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
6861
+ if (!widget)
6862
+ widget = builder.content.appendChild(document.createElement("span"));
6863
+ widget.setAttribute("cm-marker", marker.id);
6864
+ }
6293
6865
  if (widget) {
6294
- builder.map.push(builder.pos, builder.pos + size, widget);
6866
+ builder.cm.display.input.setUneditable(widget);
6295
6867
  builder.content.appendChild(widget);
6296
6868
  }
6297
6869
  builder.pos += size;
@@ -7509,14 +8081,15 @@
7509
8081
  return out;
7510
8082
  }
7511
8083
 
8084
+ function nothing() {}
8085
+
7512
8086
  function createObj(base, props) {
7513
8087
  var inst;
7514
8088
  if (Object.create) {
7515
8089
  inst = Object.create(base);
7516
8090
  } else {
7517
- var ctor = function() {};
7518
- ctor.prototype = base;
7519
- inst = new ctor();
8091
+ nothing.prototype = base;
8092
+ inst = new nothing();
7520
8093
  }
7521
8094
  if (props) copyObj(props, inst);
7522
8095
  return inst;
@@ -7571,9 +8144,9 @@
7571
8144
  }
7572
8145
 
7573
8146
  var range;
7574
- if (document.createRange) range = function(node, start, end) {
8147
+ if (document.createRange) range = function(node, start, end, endNode) {
7575
8148
  var r = document.createRange();
7576
- r.setEnd(node, end);
8149
+ r.setEnd(endNode || node, end);
7577
8150
  r.setStart(node, start);
7578
8151
  return r;
7579
8152
  };
@@ -7598,12 +8171,14 @@
7598
8171
  }
7599
8172
 
7600
8173
  var contains = CodeMirror.contains = function(parent, child) {
8174
+ if (child.nodeType == 3) // Android browser always returns false when child is a textnode
8175
+ child = child.parentNode;
7601
8176
  if (parent.contains)
7602
8177
  return parent.contains(child);
7603
- while (child = child.parentNode) {
8178
+ do {
7604
8179
  if (child.nodeType == 11) child = child.host;
7605
8180
  if (child == parent) return true;
7606
- }
8181
+ } while (child = child.parentNode);
7607
8182
  };
7608
8183
 
7609
8184
  function activeElt() { return document.activeElement; }
@@ -7689,8 +8264,10 @@
7689
8264
  if (measure.firstChild.offsetHeight != 0)
7690
8265
  zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8);
7691
8266
  }
7692
- if (zwspSupported) return elt("span", "\u200b");
7693
- else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
8267
+ var node = zwspSupported ? elt("span", "\u200b") :
8268
+ elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
8269
+ node.setAttribute("cm-text", "");
8270
+ return node;
7694
8271
  }
7695
8272
 
7696
8273
  // Feature-detect IE's crummy client rect reporting for bidi text
@@ -8062,7 +8639,7 @@
8062
8639
 
8063
8640
  // THE END
8064
8641
 
8065
- CodeMirror.version = "4.13.0";
8642
+ CodeMirror.version = "5.0.0";
8066
8643
 
8067
8644
  return CodeMirror;
8068
8645
  });