maglev-webtools 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/LICENSE.txt +25 -0
  2. data/README.rdoc +121 -0
  3. data/bin/webtools +15 -0
  4. data/lib/web_tools/#debugger.rb# +212 -0
  5. data/lib/web_tools/browser.rb +45 -0
  6. data/lib/web_tools/debugger.rb +219 -0
  7. data/lib/web_tools/info.rb +29 -0
  8. data/lib/web_tools/middleware/debugger.rb +118 -0
  9. data/lib/web_tools/support/app_model.rb +117 -0
  10. data/lib/web_tools/support/code_browser.rb +109 -0
  11. data/lib/web_tools/support/ruby.rb +132 -0
  12. data/lib/web_tools/support/service_helper.rb +22 -0
  13. data/lib/web_tools/support/smalltalk_extensions.rb +65 -0
  14. data/lib/web_tools/support/smalltalk_tools.rb +16 -0
  15. data/lib/web_tools/ui.rb +67 -0
  16. data/lib/web_tools.rb +10 -0
  17. data/public/images/favicon.ico +0 -0
  18. data/public/javascript/CodeMirror/LICENSE +23 -0
  19. data/public/javascript/CodeMirror/css/Smalltalk.css +34 -0
  20. data/public/javascript/CodeMirror/js/codemirror.js +582 -0
  21. data/public/javascript/CodeMirror/js/editor.js +1671 -0
  22. data/public/javascript/CodeMirror/js/highlight.js +68 -0
  23. data/public/javascript/CodeMirror/js/parseSmalltalk.js +126 -0
  24. data/public/javascript/CodeMirror/js/parsedummy.js +32 -0
  25. data/public/javascript/CodeMirror/js/select.js +699 -0
  26. data/public/javascript/CodeMirror/js/stringstream.js +159 -0
  27. data/public/javascript/CodeMirror/js/tokenize.js +57 -0
  28. data/public/javascript/CodeMirror/js/undo.js +413 -0
  29. data/public/javascript/CodeMirror/js/util.js +133 -0
  30. data/public/javascript/CodeMirror/testSmalltalkParser.html +116 -0
  31. data/public/javascript/ace/ace-uncompressed.js +17299 -0
  32. data/public/javascript/ace/ace.js +1 -0
  33. data/public/javascript/ace/keybinding-emacs.js +1 -0
  34. data/public/javascript/ace/keybinding-vim.js +1 -0
  35. data/public/javascript/ace/mode-c_cpp.js +1 -0
  36. data/public/javascript/ace/mode-clojure.js +1 -0
  37. data/public/javascript/ace/mode-coffee.js +1 -0
  38. data/public/javascript/ace/mode-csharp.js +1 -0
  39. data/public/javascript/ace/mode-css.js +1 -0
  40. data/public/javascript/ace/mode-groovy.js +1 -0
  41. data/public/javascript/ace/mode-html.js +1 -0
  42. data/public/javascript/ace/mode-java.js +1 -0
  43. data/public/javascript/ace/mode-javascript.js +1 -0
  44. data/public/javascript/ace/mode-json.js +1 -0
  45. data/public/javascript/ace/mode-lua.js +1 -0
  46. data/public/javascript/ace/mode-markdown.js +1 -0
  47. data/public/javascript/ace/mode-ocaml.js +1 -0
  48. data/public/javascript/ace/mode-perl.js +1 -0
  49. data/public/javascript/ace/mode-php.js +1 -0
  50. data/public/javascript/ace/mode-python.js +1 -0
  51. data/public/javascript/ace/mode-ruby.js +1 -0
  52. data/public/javascript/ace/mode-scad.js +1 -0
  53. data/public/javascript/ace/mode-scala.js +1 -0
  54. data/public/javascript/ace/mode-scss.js +1 -0
  55. data/public/javascript/ace/mode-svg.js +1 -0
  56. data/public/javascript/ace/mode-textile.js +1 -0
  57. data/public/javascript/ace/mode-xml.js +1 -0
  58. data/public/javascript/ace/theme-clouds.js +1 -0
  59. data/public/javascript/ace/theme-clouds_midnight.js +1 -0
  60. data/public/javascript/ace/theme-cobalt.js +1 -0
  61. data/public/javascript/ace/theme-crimson_editor.js +1 -0
  62. data/public/javascript/ace/theme-dawn.js +1 -0
  63. data/public/javascript/ace/theme-eclipse.js +1 -0
  64. data/public/javascript/ace/theme-idle_fingers.js +1 -0
  65. data/public/javascript/ace/theme-kr_theme.js +1 -0
  66. data/public/javascript/ace/theme-merbivore.js +1 -0
  67. data/public/javascript/ace/theme-merbivore_soft.js +1 -0
  68. data/public/javascript/ace/theme-mono_industrial.js +1 -0
  69. data/public/javascript/ace/theme-monokai.js +1 -0
  70. data/public/javascript/ace/theme-pastel_on_dark.js +1 -0
  71. data/public/javascript/ace/theme-solarized_dark.js +1 -0
  72. data/public/javascript/ace/theme-solarized_light.js +1 -0
  73. data/public/javascript/ace/theme-textmate.js +1 -0
  74. data/public/javascript/ace/theme-twilight.js +1 -0
  75. data/public/javascript/ace/theme-vibrant_ink.js +1 -0
  76. data/public/javascript/ace/worker-coffee.js +1 -0
  77. data/public/javascript/ace/worker-css.js +1 -0
  78. data/public/javascript/ace/worker-javascript.js +1 -0
  79. data/public/javascript/webtools/#debugger.coffee# +253 -0
  80. data/public/javascript/webtools/browser.js +260 -0
  81. data/public/javascript/webtools/debugger.coffee +286 -0
  82. data/public/javascript/webtools/debugger.js +366 -0
  83. data/public/javascript/webtools/sessions.coffee +17 -0
  84. data/public/javascript/webtools/sessions.js +27 -0
  85. data/public/javascript/webtools/version.coffee +14 -0
  86. data/public/javascript/webtools/version.js +20 -0
  87. data/public/stylesheets/base/images/ui-anim_basic_16x16.gif +0 -0
  88. data/public/stylesheets/base/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  89. data/public/stylesheets/base/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  90. data/public/stylesheets/base/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  91. data/public/stylesheets/base/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  92. data/public/stylesheets/base/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  93. data/public/stylesheets/base/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  94. data/public/stylesheets/base/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  95. data/public/stylesheets/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  96. data/public/stylesheets/base/images/ui-icons_222222_256x240.png +0 -0
  97. data/public/stylesheets/base/images/ui-icons_2e83ff_256x240.png +0 -0
  98. data/public/stylesheets/base/images/ui-icons_454545_256x240.png +0 -0
  99. data/public/stylesheets/base/images/ui-icons_888888_256x240.png +0 -0
  100. data/public/stylesheets/base/images/ui-icons_cd0a0a_256x240.png +0 -0
  101. data/public/stylesheets/base/jquery.ui.accordion.css +12 -0
  102. data/public/stylesheets/base/jquery.ui.all.css +2 -0
  103. data/public/stylesheets/base/jquery.ui.autocomplete.css +39 -0
  104. data/public/stylesheets/base/jquery.ui.base.css +11 -0
  105. data/public/stylesheets/base/jquery.ui.button.css +35 -0
  106. data/public/stylesheets/base/jquery.ui.core.css +37 -0
  107. data/public/stylesheets/base/jquery.ui.datepicker.css +61 -0
  108. data/public/stylesheets/base/jquery.ui.dialog.css +13 -0
  109. data/public/stylesheets/base/jquery.ui.progressbar.css +4 -0
  110. data/public/stylesheets/base/jquery.ui.resizable.css +13 -0
  111. data/public/stylesheets/base/jquery.ui.selectable.css +3 -0
  112. data/public/stylesheets/base/jquery.ui.slider.css +17 -0
  113. data/public/stylesheets/base/jquery.ui.tabs.css +11 -0
  114. data/public/stylesheets/base/jquery.ui.theme.css +247 -0
  115. data/public/stylesheets/jquery.contextMenu.css +62 -0
  116. data/public/stylesheets/reset.css +18 -0
  117. data/public/stylesheets/webtools.css +53 -0
  118. data/public/test.html +47 -0
  119. data/views/browser.rhtml +63 -0
  120. data/views/debugger.rhtml +59 -0
  121. data/views/index.rhtml +8 -0
  122. data/views/layout.rhtml +24 -0
  123. data/views/sessions.rhtml +12 -0
  124. data/views/version.rhtml +10 -0
  125. metadata +316 -0
@@ -0,0 +1,699 @@
1
+ /* Functionality for finding, storing, and restoring selections
2
+ *
3
+ * This does not provide a generic API, just the minimal functionality
4
+ * required by the CodeMirror system.
5
+ */
6
+
7
+ // Namespace object.
8
+ var select = {};
9
+
10
+ (function() {
11
+ select.ie_selection = document.selection && document.selection.createRangeCollection;
12
+
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
16
+ // node.
17
+ function topLevelNodeAt(node, top) {
18
+ while (node && node.parentNode != top)
19
+ node = node.parentNode;
20
+ return node;
21
+ }
22
+
23
+ // Find the top-level node that contains the node before this one.
24
+ function topLevelNodeBefore(node, top) {
25
+ while (!node.previousSibling && node.parentNode != top)
26
+ node = node.parentNode;
27
+ return topLevelNodeAt(node.previousSibling, top);
28
+ }
29
+
30
+ var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
31
+
32
+ select.scrollToNode = function(node, cursor) {
33
+ if (!node) return;
34
+ var element = node, body = document.body,
35
+ html = document.documentElement,
36
+ atEnd = !element.nextSibling || !element.nextSibling.nextSibling
37
+ || !element.nextSibling.nextSibling.nextSibling;
38
+ // In Opera (and recent Webkit versions), BR elements *always*
39
+ // have a offsetTop property of zero.
40
+ var compensateHack = 0;
41
+ while (element && !element.offsetTop) {
42
+ compensateHack++;
43
+ element = element.previousSibling;
44
+ }
45
+ // atEnd is another kludge for these browsers -- if the cursor is
46
+ // at the end of the document, and the node doesn't have an
47
+ // offset, just scroll to the end.
48
+ if (compensateHack == 0) atEnd = false;
49
+
50
+ // WebKit has a bad habit of (sometimes) happily returning bogus
51
+ // offsets when the document has just been changed. This seems to
52
+ // always be 5/5, so we don't use those.
53
+ if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
54
+ return;
55
+
56
+ var y = compensateHack * (element ? element.offsetHeight : 0), x = 0,
57
+ width = (node ? node.offsetWidth : 0), pos = element;
58
+ while (pos && pos.offsetParent) {
59
+ y += pos.offsetTop;
60
+ // Don't count X offset for <br> nodes
61
+ if (!isBR(pos))
62
+ x += pos.offsetLeft;
63
+ pos = pos.offsetParent;
64
+ }
65
+
66
+ var scroll_x = body.scrollLeft || html.scrollLeft || 0,
67
+ scroll_y = body.scrollTop || html.scrollTop || 0,
68
+ scroll = false, screen_width = window.innerWidth || html.clientWidth || 0;
69
+
70
+ if (cursor || width < screen_width) {
71
+ if (cursor) {
72
+ var off = select.offsetInNode(node), size = nodeText(node).length;
73
+ if (size) x += width * (off / size);
74
+ }
75
+ var screen_x = x - scroll_x;
76
+ if (screen_x < 0 || screen_x > screen_width) {
77
+ scroll_x = x;
78
+ scroll = true;
79
+ }
80
+ }
81
+ var screen_y = y - scroll_y;
82
+ if (screen_y < 0 || atEnd || screen_y > (window.innerHeight || html.clientHeight || 0) - 50) {
83
+ scroll_y = atEnd ? 1e6 : y;
84
+ scroll = true;
85
+ }
86
+ if (scroll) window.scrollTo(scroll_x, scroll_y);
87
+ };
88
+
89
+ select.scrollToCursor = function(container) {
90
+ select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild, true);
91
+ };
92
+
93
+ // Used to prevent restoring a selection when we do not need to.
94
+ var currentSelection = null;
95
+
96
+ select.snapshotChanged = function() {
97
+ if (currentSelection) currentSelection.changed = true;
98
+ };
99
+
100
+ // Find the 'leaf' node (BR or text) after the given one.
101
+ function baseNodeAfter(node) {
102
+ var next = node.nextSibling;
103
+ if (next) {
104
+ while (next.firstChild) next = next.firstChild;
105
+ if (next.nodeType == 3 || isBR(next)) return next;
106
+ else return baseNodeAfter(next);
107
+ }
108
+ else {
109
+ var parent = node.parentNode;
110
+ while (parent && !parent.nextSibling) parent = parent.parentNode;
111
+ return parent && baseNodeAfter(parent);
112
+ }
113
+ }
114
+
115
+ // This is called by the code in editor.js whenever it is replacing
116
+ // a text node. The function sees whether the given oldNode is part
117
+ // of the current selection, and updates this selection if it is.
118
+ // Because nodes are often only partially replaced, the length of
119
+ // the part that gets replaced has to be taken into account -- the
120
+ // selection might stay in the oldNode if the newNode is smaller
121
+ // than the selection's offset. The offset argument is needed in
122
+ // case the selection does move to the new object, and the given
123
+ // length is not the whole length of the new node (part of it might
124
+ // have been used to replace another node).
125
+ select.snapshotReplaceNode = function(from, to, length, offset) {
126
+ if (!currentSelection) return;
127
+
128
+ function replace(point) {
129
+ if (from == point.node) {
130
+ currentSelection.changed = true;
131
+ if (length && point.offset > length) {
132
+ point.offset -= length;
133
+ }
134
+ else {
135
+ point.node = to;
136
+ point.offset += (offset || 0);
137
+ }
138
+ }
139
+ else if (select.ie_selection && point.offset == 0 && point.node == baseNodeAfter(from)) {
140
+ currentSelection.changed = true;
141
+ }
142
+ }
143
+ replace(currentSelection.start);
144
+ replace(currentSelection.end);
145
+ };
146
+
147
+ select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
148
+ if (!currentSelection) return;
149
+
150
+ function move(point) {
151
+ if (from == point.node && (!ifAtStart || point.offset == 0)) {
152
+ currentSelection.changed = true;
153
+ point.node = to;
154
+ if (relative) point.offset = Math.max(0, point.offset + distance);
155
+ else point.offset = distance;
156
+ }
157
+ }
158
+ move(currentSelection.start);
159
+ move(currentSelection.end);
160
+ };
161
+
162
+ // Most functions are defined in two ways, one for the IE selection
163
+ // model, one for the W3C one.
164
+ if (select.ie_selection) {
165
+ function selRange() {
166
+ var sel = document.selection;
167
+ if (!sel) return null;
168
+ if (sel.createRange) return sel.createRange();
169
+ else return sel.createTextRange();
170
+ }
171
+
172
+ function selectionNode(start) {
173
+ var range = selRange();
174
+ range.collapse(start);
175
+
176
+ function nodeAfter(node) {
177
+ var found = null;
178
+ while (!found && node) {
179
+ found = node.nextSibling;
180
+ node = node.parentNode;
181
+ }
182
+ return nodeAtStartOf(found);
183
+ }
184
+
185
+ function nodeAtStartOf(node) {
186
+ while (node && node.firstChild) node = node.firstChild;
187
+ return {node: node, offset: 0};
188
+ }
189
+
190
+ var containing = range.parentElement();
191
+ if (!isAncestor(document.body, containing)) return null;
192
+ if (!containing.firstChild) return nodeAtStartOf(containing);
193
+
194
+ var working = range.duplicate();
195
+ working.moveToElementText(containing);
196
+ working.collapse(true);
197
+ for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
198
+ if (cur.nodeType == 3) {
199
+ var size = cur.nodeValue.length;
200
+ working.move("character", size);
201
+ }
202
+ else {
203
+ working.moveToElementText(cur);
204
+ working.collapse(false);
205
+ }
206
+
207
+ var dir = range.compareEndPoints("StartToStart", working);
208
+ if (dir == 0) return nodeAfter(cur);
209
+ if (dir == 1) continue;
210
+ if (cur.nodeType != 3) return nodeAtStartOf(cur);
211
+
212
+ working.setEndPoint("StartToEnd", range);
213
+ return {node: cur, offset: size - working.text.length};
214
+ }
215
+ return nodeAfter(containing);
216
+ }
217
+
218
+ select.markSelection = function() {
219
+ currentSelection = null;
220
+ var sel = document.selection;
221
+ if (!sel) return;
222
+ var start = selectionNode(true),
223
+ end = selectionNode(false);
224
+ if (!start || !end) return;
225
+ currentSelection = {start: start, end: end, changed: false};
226
+ };
227
+
228
+ select.selectMarked = function() {
229
+ if (!currentSelection || !currentSelection.changed) return;
230
+
231
+ function makeRange(point) {
232
+ var range = document.body.createTextRange(),
233
+ node = point.node;
234
+ if (!node) {
235
+ range.moveToElementText(document.body);
236
+ range.collapse(false);
237
+ }
238
+ else if (node.nodeType == 3) {
239
+ range.moveToElementText(node.parentNode);
240
+ var offset = point.offset;
241
+ while (node.previousSibling) {
242
+ node = node.previousSibling;
243
+ offset += (node.innerText || "").length;
244
+ }
245
+ range.move("character", offset);
246
+ }
247
+ else {
248
+ range.moveToElementText(node);
249
+ range.collapse(true);
250
+ }
251
+ return range;
252
+ }
253
+
254
+ var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
255
+ start.setEndPoint("StartToEnd", end);
256
+ start.select();
257
+ };
258
+
259
+ select.offsetInNode = function(node) {
260
+ var range = selRange();
261
+ if (!range) return 0;
262
+ var range2 = range.duplicate();
263
+ try {range2.moveToElementText(node);} catch(e){return 0;}
264
+ range.setEndPoint("StartToStart", range2);
265
+ return range.text.length;
266
+ };
267
+
268
+ // Get the top-level node that one end of the cursor is inside or
269
+ // after. Note that this returns false for 'no cursor', and null
270
+ // for 'start of document'.
271
+ select.selectionTopNode = function(container, start) {
272
+ var range = selRange();
273
+ if (!range) return false;
274
+ var range2 = range.duplicate();
275
+ range.collapse(start);
276
+ var around = range.parentElement();
277
+ if (around && isAncestor(container, around)) {
278
+ // Only use this node if the selection is not at its start.
279
+ range2.moveToElementText(around);
280
+ if (range.compareEndPoints("StartToStart", range2) == 1)
281
+ return topLevelNodeAt(around, container);
282
+ }
283
+
284
+ // Move the start of a range to the start of a node,
285
+ // compensating for the fact that you can't call
286
+ // moveToElementText with text nodes.
287
+ function moveToNodeStart(range, node) {
288
+ if (node.nodeType == 3) {
289
+ var count = 0, cur = node.previousSibling;
290
+ while (cur && cur.nodeType == 3) {
291
+ count += cur.nodeValue.length;
292
+ cur = cur.previousSibling;
293
+ }
294
+ if (cur) {
295
+ try{range.moveToElementText(cur);}
296
+ catch(e){return false;}
297
+ range.collapse(false);
298
+ }
299
+ else range.moveToElementText(node.parentNode);
300
+ if (count) range.move("character", count);
301
+ }
302
+ else {
303
+ try{range.moveToElementText(node);}
304
+ catch(e){return false;}
305
+ }
306
+ return true;
307
+ }
308
+
309
+ // Do a binary search through the container object, comparing
310
+ // the start of each node to the selection
311
+ var start = 0, end = container.childNodes.length - 1;
312
+ while (start < end) {
313
+ var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
314
+ if (!node) return false; // Don't ask. IE6 manages this sometimes.
315
+ if (!moveToNodeStart(range2, node)) return false;
316
+ if (range.compareEndPoints("StartToStart", range2) == 1)
317
+ start = middle;
318
+ else
319
+ end = middle - 1;
320
+ }
321
+
322
+ if (start == 0) {
323
+ var test1 = selRange(), test2 = test1.duplicate();
324
+ try {
325
+ test2.moveToElementText(container);
326
+ } catch(exception) {
327
+ return null;
328
+ }
329
+ if (test1.compareEndPoints("StartToStart", test2) == 0)
330
+ return null;
331
+ }
332
+ return container.childNodes[start] || null;
333
+ };
334
+
335
+ // Place the cursor after this.start. This is only useful when
336
+ // manually moving the cursor instead of restoring it to its old
337
+ // position.
338
+ select.focusAfterNode = function(node, container) {
339
+ var range = document.body.createTextRange();
340
+ range.moveToElementText(node || container);
341
+ range.collapse(!node);
342
+ range.select();
343
+ };
344
+
345
+ select.somethingSelected = function() {
346
+ var range = selRange();
347
+ return range && (range.text != "");
348
+ };
349
+
350
+ function insertAtCursor(html) {
351
+ var range = selRange();
352
+ if (range) {
353
+ range.pasteHTML(html);
354
+ range.collapse(false);
355
+ range.select();
356
+ }
357
+ }
358
+
359
+ // Used to normalize the effect of the enter key, since browsers
360
+ // do widely different things when pressing enter in designMode.
361
+ select.insertNewlineAtCursor = function() {
362
+ insertAtCursor("<br>");
363
+ };
364
+
365
+ select.insertTabAtCursor = function() {
366
+ insertAtCursor(fourSpaces);
367
+ };
368
+
369
+ // Get the BR node at the start of the line on which the cursor
370
+ // currently is, and the offset into the line. Returns null as
371
+ // node if cursor is on first line.
372
+ select.cursorPos = function(container, start) {
373
+ var range = selRange();
374
+ if (!range) return null;
375
+
376
+ var topNode = select.selectionTopNode(container, start);
377
+ while (topNode && !isBR(topNode))
378
+ topNode = topNode.previousSibling;
379
+
380
+ var range2 = range.duplicate();
381
+ range.collapse(start);
382
+ if (topNode) {
383
+ range2.moveToElementText(topNode);
384
+ range2.collapse(false);
385
+ }
386
+ else {
387
+ // When nothing is selected, we can get all kinds of funky errors here.
388
+ try { range2.moveToElementText(container); }
389
+ catch (e) { return null; }
390
+ range2.collapse(true);
391
+ }
392
+ range.setEndPoint("StartToStart", range2);
393
+
394
+ return {node: topNode, offset: range.text.length};
395
+ };
396
+
397
+ select.setCursorPos = function(container, from, to) {
398
+ function rangeAt(pos) {
399
+ var range = document.body.createTextRange();
400
+ if (!pos.node) {
401
+ range.moveToElementText(container);
402
+ range.collapse(true);
403
+ }
404
+ else {
405
+ range.moveToElementText(pos.node);
406
+ range.collapse(false);
407
+ }
408
+ range.move("character", pos.offset);
409
+ return range;
410
+ }
411
+
412
+ var range = rangeAt(from);
413
+ if (to && to != from)
414
+ range.setEndPoint("EndToEnd", rangeAt(to));
415
+ range.select();
416
+ }
417
+
418
+ // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
419
+ select.getBookmark = function (container) {
420
+ var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
421
+ if (from && to) return {from: from, to: to};
422
+ };
423
+
424
+ // Restore a stored selection.
425
+ select.setBookmark = function(container, mark) {
426
+ if (!mark) return;
427
+ select.setCursorPos(container, mark.from, mark.to);
428
+ };
429
+ }
430
+ // W3C model
431
+ else {
432
+ // Find the node right at the cursor, not one of its
433
+ // ancestors with a suitable offset. This goes down the DOM tree
434
+ // until a 'leaf' is reached (or is it *up* the DOM tree?).
435
+ function innerNode(node, offset) {
436
+ while (node.nodeType != 3 && !isBR(node)) {
437
+ var newNode = node.childNodes[offset] || node.nextSibling;
438
+ offset = 0;
439
+ while (!newNode && node.parentNode) {
440
+ node = node.parentNode;
441
+ newNode = node.nextSibling;
442
+ }
443
+ node = newNode;
444
+ if (!newNode) break;
445
+ }
446
+ return {node: node, offset: offset};
447
+ }
448
+
449
+ // Store start and end nodes, and offsets within these, and refer
450
+ // back to the selection object from those nodes, so that this
451
+ // object can be updated when the nodes are replaced before the
452
+ // selection is restored.
453
+ select.markSelection = function () {
454
+ var selection = window.getSelection();
455
+ if (!selection || selection.rangeCount == 0)
456
+ return (currentSelection = null);
457
+ var range = selection.getRangeAt(0);
458
+
459
+ currentSelection = {
460
+ start: innerNode(range.startContainer, range.startOffset),
461
+ end: innerNode(range.endContainer, range.endOffset),
462
+ changed: false
463
+ };
464
+ };
465
+
466
+ select.selectMarked = function () {
467
+ var cs = currentSelection;
468
+ // on webkit-based browsers, it is apparently possible that the
469
+ // selection gets reset even when a node that is not one of the
470
+ // endpoints get messed with. the most common situation where
471
+ // this occurs is when a selection is deleted or overwitten. we
472
+ // check for that here.
473
+ function focusIssue() {
474
+ if (cs.start.node == cs.end.node && cs.start.offset == cs.end.offset) {
475
+ var selection = window.getSelection();
476
+ if (!selection || selection.rangeCount == 0) return true;
477
+ var range = selection.getRangeAt(0), point = innerNode(range.startContainer, range.startOffset);
478
+ return cs.start.node != point.node || cs.start.offset != point.offset;
479
+ }
480
+ }
481
+ if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
482
+ var range = document.createRange();
483
+
484
+ function setPoint(point, which) {
485
+ if (point.node) {
486
+ // Some magic to generalize the setting of the start and end
487
+ // of a range.
488
+ if (point.offset == 0)
489
+ range["set" + which + "Before"](point.node);
490
+ else
491
+ range["set" + which](point.node, point.offset);
492
+ }
493
+ else {
494
+ range.setStartAfter(document.body.lastChild || document.body);
495
+ }
496
+ }
497
+
498
+ setPoint(cs.end, "End");
499
+ setPoint(cs.start, "Start");
500
+ selectRange(range);
501
+ };
502
+
503
+ // Helper for selecting a range object.
504
+ function selectRange(range) {
505
+ var selection = window.getSelection();
506
+ if (!selection) return;
507
+ selection.removeAllRanges();
508
+ selection.addRange(range);
509
+ }
510
+ function selectionRange() {
511
+ var selection = window.getSelection();
512
+ if (!selection || selection.rangeCount == 0)
513
+ return false;
514
+ else
515
+ return selection.getRangeAt(0);
516
+ }
517
+
518
+ // Finding the top-level node at the cursor in the W3C is, as you
519
+ // can see, quite an involved process.
520
+ select.selectionTopNode = function(container, start) {
521
+ var range = selectionRange();
522
+ if (!range) return false;
523
+
524
+ var node = start ? range.startContainer : range.endContainer;
525
+ var offset = start ? range.startOffset : range.endOffset;
526
+ // Work around (yet another) bug in Opera's selection model.
527
+ if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
528
+ container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
529
+ offset--;
530
+
531
+ // For text nodes, we look at the node itself if the cursor is
532
+ // inside, or at the node before it if the cursor is at the
533
+ // start.
534
+ if (node.nodeType == 3){
535
+ if (offset > 0)
536
+ return topLevelNodeAt(node, container);
537
+ else
538
+ return topLevelNodeBefore(node, container);
539
+ }
540
+ // Occasionally, browsers will return the HTML node as
541
+ // selection. If the offset is 0, we take the start of the frame
542
+ // ('after null'), otherwise, we take the last node.
543
+ else if (node.nodeName.toUpperCase() == "HTML") {
544
+ return (offset == 1 ? null : container.lastChild);
545
+ }
546
+ // If the given node is our 'container', we just look up the
547
+ // correct node by using the offset.
548
+ else if (node == container) {
549
+ return (offset == 0) ? null : node.childNodes[offset - 1];
550
+ }
551
+ // In any other case, we have a regular node. If the cursor is
552
+ // at the end of the node, we use the node itself, if it is at
553
+ // the start, we use the node before it, and in any other
554
+ // case, we look up the child before the cursor and use that.
555
+ else {
556
+ if (offset == node.childNodes.length)
557
+ return topLevelNodeAt(node, container);
558
+ else if (offset == 0)
559
+ return topLevelNodeBefore(node, container);
560
+ else
561
+ return topLevelNodeAt(node.childNodes[offset - 1], container);
562
+ }
563
+ };
564
+
565
+ select.focusAfterNode = function(node, container) {
566
+ var range = document.createRange();
567
+ range.setStartBefore(container.firstChild || container);
568
+ // In Opera, setting the end of a range at the end of a line
569
+ // (before a BR) will cause the cursor to appear on the next
570
+ // line, so we set the end inside of the start node when
571
+ // possible.
572
+ if (node && !node.firstChild)
573
+ range.setEndAfter(node);
574
+ else if (node)
575
+ range.setEnd(node, node.childNodes.length);
576
+ else
577
+ range.setEndBefore(container.firstChild || container);
578
+ range.collapse(false);
579
+ selectRange(range);
580
+ };
581
+
582
+ select.somethingSelected = function() {
583
+ var range = selectionRange();
584
+ return range && !range.collapsed;
585
+ };
586
+
587
+ select.offsetInNode = function(node) {
588
+ var range = selectionRange();
589
+ if (!range) return 0;
590
+ range = range.cloneRange();
591
+ range.setStartBefore(node);
592
+ return range.toString().length;
593
+ };
594
+
595
+ select.insertNodeAtCursor = function(node) {
596
+ var range = selectionRange();
597
+ if (!range) return;
598
+
599
+ range.deleteContents();
600
+ range.insertNode(node);
601
+ webkitLastLineHack(document.body);
602
+
603
+ // work around weirdness where Opera will magically insert a new
604
+ // BR node when a BR node inside a span is moved around. makes
605
+ // sure the BR ends up outside of spans.
606
+ if (window.opera && isBR(node) && isSpan(node.parentNode)) {
607
+ var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
608
+ outer.insertBefore(node, p.nextSibling);
609
+ var textAfter = "";
610
+ for (; next && next.nodeType == 3; next = next.nextSibling) {
611
+ textAfter += next.nodeValue;
612
+ removeElement(next);
613
+ }
614
+ outer.insertBefore(makePartSpan(textAfter, document), node.nextSibling);
615
+ }
616
+ range = document.createRange();
617
+ range.selectNode(node);
618
+ range.collapse(false);
619
+ selectRange(range);
620
+ }
621
+
622
+ select.insertNewlineAtCursor = function() {
623
+ select.insertNodeAtCursor(document.createElement("BR"));
624
+ };
625
+
626
+ select.insertTabAtCursor = function() {
627
+ select.insertNodeAtCursor(document.createTextNode(fourSpaces));
628
+ };
629
+
630
+ select.cursorPos = function(container, start) {
631
+ var range = selectionRange();
632
+ if (!range) return;
633
+
634
+ var topNode = select.selectionTopNode(container, start);
635
+ while (topNode && !isBR(topNode))
636
+ topNode = topNode.previousSibling;
637
+
638
+ range = range.cloneRange();
639
+ range.collapse(start);
640
+ if (topNode)
641
+ range.setStartAfter(topNode);
642
+ else
643
+ range.setStartBefore(container);
644
+
645
+ var text = range.toString();
646
+ return {node: topNode, offset: text.length};
647
+ };
648
+
649
+ select.setCursorPos = function(container, from, to) {
650
+ var range = document.createRange();
651
+
652
+ function setPoint(node, offset, side) {
653
+ if (offset == 0 && node && !node.nextSibling) {
654
+ range["set" + side + "After"](node);
655
+ return true;
656
+ }
657
+
658
+ if (!node)
659
+ node = container.firstChild;
660
+ else
661
+ node = node.nextSibling;
662
+
663
+ if (!node) return;
664
+
665
+ if (offset == 0) {
666
+ range["set" + side + "Before"](node);
667
+ return true;
668
+ }
669
+
670
+ var backlog = []
671
+ function decompose(node) {
672
+ if (node.nodeType == 3)
673
+ backlog.push(node);
674
+ else
675
+ forEach(node.childNodes, decompose);
676
+ }
677
+ while (true) {
678
+ while (node && !backlog.length) {
679
+ decompose(node);
680
+ node = node.nextSibling;
681
+ }
682
+ var cur = backlog.shift();
683
+ if (!cur) return false;
684
+
685
+ var length = cur.nodeValue.length;
686
+ if (length >= offset) {
687
+ range["set" + side](cur, offset);
688
+ return true;
689
+ }
690
+ offset -= length;
691
+ }
692
+ }
693
+
694
+ to = to || from;
695
+ if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
696
+ selectRange(range);
697
+ };
698
+ }
699
+ })();