codemirror-rails 4.13 → 5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/codemirror/rails/version.rb +2 -2
- data/vendor/assets/javascripts/codemirror.js +1025 -448
- data/vendor/assets/javascripts/codemirror/addons/edit/matchbrackets.js +1 -1
- data/vendor/assets/stylesheets/codemirror.css +13 -2
- data/vendor/assets/stylesheets/codemirror/themes/3024-day.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/3024-night.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/ambiance.css +4 -6
- data/vendor/assets/stylesheets/codemirror/themes/base16-dark.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/base16-light.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/blackboard.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/cobalt.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/erlang-dark.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/lesser-dark.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/mbo.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/mdn-like.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/midnight.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/monokai.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/night.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/paraiso-dark.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/paraiso-light.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/pastel-on-dark.css +3 -0
- data/vendor/assets/stylesheets/codemirror/themes/rubyblue.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/solarized.css +6 -6
- data/vendor/assets/stylesheets/codemirror/themes/the-matrix.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/tomorrow-night-eighties.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/twilight.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/vibrant-ink.css +2 -0
- data/vendor/assets/stylesheets/codemirror/themes/xq-dark.css +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee58ad7606652b7d8522d0aa8e8bebba57fefa10
|
4
|
+
data.tar.gz: 23919e1e33923f699a4198fe5244d1018db115c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e40c4eec1f07c990ab495b1a316f51c41645ad87ad159b62bc45061ade6dff34ea60cea1902adc1e5e38839f5179facccbb3041b8156549907fb5a72a00253f6
|
7
|
+
data.tar.gz: 4e4b07ea9cd230ae38699c606f2810537e5ee9d0fe546c3a188749137c3124359ba112eb85245404f83e1cd4f8e999a4cf836e816fc7f69b34d3158c31c0a22b
|
@@ -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
|
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)
|
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
|
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(
|
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) ||
|
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.
|
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
|
-
|
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(
|
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
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
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
|
-
|
1375
|
-
|
1376
|
-
|
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
|
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(
|
1778
|
-
while (
|
1779
|
-
if (ie && ie_version < 9 && start == 0 && end ==
|
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.
|
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.
|
2198
|
-
showSelection(
|
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
|
-
|
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
|
-
|
2762
|
-
|
2763
|
-
|
2764
|
-
|
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(
|
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
|
-
|
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();
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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 ((
|
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
|
-
|
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
|
-
//
|
3431
|
-
// menu is closed (since 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
|
-
|
3435
|
-
if (webkit) setTimeout(
|
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 (
|
3458
|
-
|
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();
|
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
|
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
|
-
|
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)
|
5246
|
+
if (!val) cm.display.input.reset();
|
4686
5247
|
}
|
4687
5248
|
});
|
4688
|
-
option("disableInput", false, function(cm, val) {if (!val)
|
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.
|
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.
|
5143
|
-
options.tabindex = textarea.
|
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.
|
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
|
-
|
7518
|
-
|
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
|
-
|
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
|
-
|
7693
|
-
|
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 = "
|
8642
|
+
CodeMirror.version = "5.0.0";
|
8066
8643
|
|
8067
8644
|
return CodeMirror;
|
8068
8645
|
});
|