68369c27627c899852f5e75f9e8dda99ff668d40
1 /* Functionality for finding, storing, and re-storing selections
3 * This does not provide a generic API, just the minimal functionality
4 * required by the CodeMirror system.
11 var ie_selection
= document
.selection
&& document
.selection
.createRangeCollection
;
13 // Find the 'top-level' (defined as 'a direct child of the node
14 // passed as the top argument') node that the given node is
15 // contained in. Return null if the given node is not inside the top
17 function topLevelNodeAt(node
, top
) {
18 while (node
&& node
.parentNode
!= top
)
19 node
= node
.parentNode
;
23 // Find the top-level node that contains the node before this one.
24 function topLevelNodeBefore(node
, top
) {
28 while (!node
.previousSibling
&& node
.parentNode
!= top
){
29 node
= node
.parentNode
;
32 return topLevelNodeAt(node
.previousSibling
, top
);
35 // Most functions are defined in two ways, one for the IE selection
36 // model, one for the W3C one.
38 // Store the current selection in such a way that it can be
39 // restored after we manipulated the DOM tree. For IE, we store
41 select
.markSelection = function (win
) {
42 var selection
= win
.document
.selection
;
43 var start
= selection
.createRange(), end
= start
.duplicate();
47 var body
= win
.document
.body
;
48 // And we better hope no fool gave this window a padding or a
49 // margin, or all these computations will be in vain.
50 return {start
: {x
: start
.boundingLeft
+ body
.scrollLeft
- 1,
51 y
: start
.boundingTop
+ body
.scrollTop
},
52 end
: {x
: end
.boundingLeft
+ body
.scrollLeft
- 1,
53 y
: end
.boundingTop
+ body
.scrollTop
},
57 // Restore a stored selection.
58 select
.selectMarked = function(sel
) {
61 var range1
= sel
.window
.document
.body
.createTextRange(), range2
= range1
.duplicate();
62 range1
.moveToPoint(sel
.start
.x
, sel
.start
.y
);
63 range2
.moveToPoint(sel
.end
.x
, sel
.end
.y
);
64 range1
.setEndPoint("EndToStart", range2
);
68 // Not needed in IE model -- see W3C model.
69 select
.replaceSelection = function(){};
71 // A Cursor object represents a top-level node that the cursor is
72 // currently in or after. It is not possible to reliably get more
73 // detailed information, but just this node is enough for most
75 select
.Cursor = function(container
) {
76 this.container
= container
;
77 this.doc
= container
.ownerDocument
;
78 var selection
= this.doc
.selection
;
79 this.valid
= !!selection
;
81 var range
= selection
.createRange();
82 range
.collapse(false);
83 var around
= range
.parentElement();
84 if (around
&& isAncestor(container
, around
)) {
85 this.start
= topLevelNodeAt(around
, container
);
88 range
.pasteHTML("<span id='// temp //'></span>");
89 var temp
= this.doc
.getElementById("// temp //");
90 this.start
= topLevelNodeBefore(temp
, container
);
97 // Place the cursor after this.start. This is only useful when
98 // manually moving the cursor instead of restoring it to its old
100 select
.Cursor
.prototype.focus = function () {
101 var range
= this.doc
.body
.createTextRange();
102 range
.moveToElementText(this.start
|| this.container
);
103 range
.collapse(!this.start
);
108 // Used to normalize the effect of the enter key, since browsers
109 // do widely different things when pressing enter in designMode.
110 select
.insertNewlineAtCursor = function(window
) {
111 var selection
= window
.document
.selection
;
113 var range
= selection
.createRange();
114 range
.pasteHTML("<br/>");
115 range
.collapse(false);
120 // Insert a custom string at current cursor position (added for t3editor)
121 select
.insertTextAtCursor = function(window
,text
) {
122 var selection
= window
.document
.selection
;
124 var range
= selection
.createRange();
125 range
.pasteHTML(text
);
126 range
.collapse(false);
134 // Well, Opera isn't even supported at the moment, but it almost
135 // is, and this is used to fix an issue with getting the scroll
137 var opera_scroll
= !window
.scrollX
&& !window
.scrollY
;
139 // Store start and end nodes, and offsets within these, and refer
140 // back to the selection object from those nodes, so that this
141 // object can be updated when the nodes are replaced before the
142 // selection is restored.
143 select
.markSelection = function (win
) {
144 var selection
= win
.getSelection();
145 if (!selection
|| selection
.rangeCount
== 0)
147 var range
= selection
.getRangeAt(0);
149 var result
= {start
: {node
: range
.startContainer
, offset
: range
.startOffset
},
150 end
: {node
: range
.endContainer
, offset
: range
.endOffset
},
152 scrollX
: opera_scroll
&& win
.document
.body
.scrollLeft
,
153 scrollY
: opera_scroll
&& win
.document
.body
.scrollTop
};
155 // We want the nodes right at the cursor, not one of their
156 // ancestors with a suitable offset. This goes down the DOM tree
157 // until a 'leaf' is reached (or is it *up* the DOM tree?).
158 function normalize(point
){
159 while (point
.node
.nodeType
!= 3 && point
.node
.nodeName
!= "BR") {
160 var newNode
= point
.node
.childNodes
[point
.offset
] || point
.node
.nextSibling
;
162 while (!newNode
&& point
.node
.parentNode
) {
163 point
.node
= point
.node
.parentNode
;
164 newNode
= point
.node
.nextSibling
;
166 point
.node
= newNode
;
172 normalize(result
.start
);
173 normalize(result
.end
);
174 // Make the links back to the selection object (see
175 // replaceSelection).
176 if (result
.start
.node
)
177 result
.start
.node
.selectStart
= result
.start
;
179 result
.end
.node
.selectEnd
= result
.end
;
184 // Helper for selecting a range object.
185 function selectRange(range
, window
) {
186 var selection
= window
.getSelection();
187 selection
.removeAllRanges();
188 selection
.addRange(range
);
191 select
.selectMarked = function (sel
) {
194 var win
= sel
.window
;
195 var range
= win
.document
.createRange();
197 function setPoint(point
, which
) {
199 // Remove the link back to the selection.
200 delete point
.node
["select" + which
];
201 // Some magic to generalize the setting of the start and end
203 if (point
.offset
== 0)
204 range
["set" + which
+ "Before"](point
.node
);
206 range
["set" + which
](point
.node
, point
.offset
);
209 range
.setStartAfter(win
.document
.body
.lastChild
|| win
.document
.body
);
213 // Have to restore the scroll position of the frame in Opera.
215 sel
.window
.document
.body
.scrollLeft
= sel
.scrollX
;
216 sel
.window
.document
.body
.scrollTop
= sel
.scrollY
;
219 setPoint(sel
.start
, "Start");
220 setPoint(sel
.end
, "End");
221 selectRange(range
, win
);
225 // This is called by the code in codemirror.js whenever it is
226 // replacing a part of the DOM tree. The function sees whether the
227 // given oldNode is part of the current selection, and updates
228 // this selection if it is. Because nodes are often only partially
229 // replaced, the length of the part that gets replaced has to be
230 // taken into account -- the selection might stay in the oldNode
231 // if the newNode is smaller than the selection's offset. The
232 // offset argument is needed in case the selection does move to
233 // the new object, and the given length is not the whole length of
234 // the new node (part of it might have been used to replace
236 select
.replaceSelection = function(oldNode
, newNode
, length
, offset
) {
237 function replace(which
) {
238 var selObj
= oldNode
["select" + which
];
240 if (selObj
.offset
> length
) {
241 selObj
.offset
-= length
;
244 newNode
["select" + which
] = selObj
;
245 delete oldNode
["select" + which
];
246 selObj
.node
= newNode
;
247 selObj
.offset
+= (offset
|| 0);
255 // Finding the top-level node at the cursor in the W3C is, as you
256 // can see, quite an involved process. [Some of this can probably
257 // be simplified, but I'm afraid to touch it now that it finally
259 select
.Cursor = function(container
) {
260 this.container
= container
;
261 this.win
= container
.ownerDocument
.defaultView
;
262 var selection
= this.win
.getSelection();
263 this.valid
= selection
&& selection
.rangeCount
> 0;
265 var range
= selection
.getRangeAt(0);
266 var end
= range
.endContainer
;
267 // For text nodes, we look at the node itself if the cursor is
268 // inside, or at the node before it if the cursor is at the
271 if (end
.nodeType
== 3){
272 if (range
.endOffset
> 0)
273 this.start
= topLevelNodeAt(end
, this.container
);
275 this.start
= topLevelNodeBefore(end
, this.container
);
277 // Occasionally, browsers will return the HTML node as
278 // selection (Opera does this all the time, which is the
279 // reason this editor does not work on that browser). If the
280 // offset is 0, we take the start of the frame ('after null'),
281 // otherwise, we take the last node.
282 else if (end
.nodeName
== "HTML") {
283 this.start
= (range
.endOffset
== 1 ? null : container
.lastChild
);
285 // If the given node is our 'container', we just look up the
286 // correct node by using the offset.
287 else if (end
== container
) {
288 if (range
.endOffset
== 0)
291 this.start
= end
.childNodes
[range
.endOffset
- 1];
293 // In any other case, we have a regular node. If the cursor is
294 // at the end of the node, we use the node itself, if it is at
295 // the start, we use the node before it, and in any other
296 // case, we look up the child before the cursor and use that.
298 if (range
.endOffset
== end
.childNodes
.length
)
299 this.start
= topLevelNodeAt(end
, this.container
);
300 else if (range
.endOffset
== 0)
301 this.start
= topLevelNodeBefore(end
, this.container
);
303 this.start
= topLevelNodeAt(end
.childNodes
[range
.endOffset
- 1], this.container
);
308 select
.Cursor
.prototype.focus = function() {
309 var sel
= this.win
.getSelection();
310 var range
= this.win
.document
.createRange();
311 range
.setStartBefore(this.container
.firstChild
|| this.container
);
313 range
.setEndAfter(this.start
);
315 range
.setEndBefore(this.container
.firstChild
|| this.container
);
317 range
.collapse(false);
318 selectRange(range
, this.win
);
323 select
.insertNewlineAtCursor = function(window
) {
324 var selection
= window
.getSelection();
325 if (selection
&& selection
.rangeCount
> 0) {
326 var range
= selection
.getRangeAt(0);
327 var br
= withDocument(window
.document
, BR
);
328 range
.insertNode(br
);
329 range
.setEndAfter(br
);
330 range
.collapse(false);
331 selectRange(range
, window
);
335 // added for t3editor
336 select
.insertTextAtCursor = function(window
,text
) {
337 var selection
= window
.getSelection();
338 if (selection
&& selection
.rangeCount
> 0) {
339 var range
= selection
.getRangeAt(0);
340 // var br = withDocument(window.document, BR);
341 textnode
= window
.document
.createTextNode(text
);
342 range
.insertNode(textnode
);
343 range
.setEndAfter(textnode
);
344 range
.collapse(false);
345 selectRange(range
, window
);
350 // Search backwards through the top-level nodes until the next BR or
351 // the start of the frame.
352 select
.Cursor
.prototype.startOfLine = function() {
353 var start
= this.start
|| this.container
.firstChild
;
354 while (start
&& start
.nodeName
!= "BR")
355 start
= start
.previousSibling
;