codemirror-rails 4.13 → 5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  });