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.
- 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
|
});
|