##// END OF EJS Templates
Audit .html() calls take #2
Jonathan Frederic -
Show More
@@ -1,570 +1,571 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11 /**
12 12 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule CodeCell
16 16 */
17 17
18 18
19 19 /* local util for codemirror */
20 20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21 21
22 22 /**
23 23 *
24 24 * function to delete until previous non blanking space character
25 25 * or first multiple of 4 tabstop.
26 26 * @private
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 35 var select = cm.getRange(from,cur);
36 36 if( select.match(/^\ +$/) !== null){
37 37 cm.replaceRange("",from,cur);
38 38 } else {
39 39 cm.deleteH(-1,"char");
40 40 }
41 41 };
42 42
43 43
44 44 var IPython = (function (IPython) {
45 45 "use strict";
46 46
47 47 var utils = IPython.utils;
48 48 var key = IPython.utils.keycodes;
49 49
50 50 /**
51 51 * A Cell conceived to write code.
52 52 *
53 53 * The kernel doesn't have to be set at creation time, in that case
54 54 * it will be null and set_kernel has to be called later.
55 55 * @class CodeCell
56 56 * @extends IPython.Cell
57 57 *
58 58 * @constructor
59 59 * @param {Object|null} kernel
60 60 * @param {object|undefined} [options]
61 61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 62 */
63 63 var CodeCell = function (kernel, options) {
64 64 this.kernel = kernel || null;
65 65 this.collapsed = false;
66 66
67 67 // create all attributed in constructor function
68 68 // even if null for V8 VM optimisation
69 69 this.input_prompt_number = null;
70 70 this.celltoolbar = null;
71 71 this.output_area = null;
72 72 this.last_msg_id = null;
73 73 this.completer = null;
74 74
75 75
76 76 var cm_overwrite_options = {
77 77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 78 };
79 79
80 80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81 81
82 82 IPython.Cell.apply(this,[options]);
83 83
84 84 // Attributes we want to override in this subclass.
85 85 this.cell_type = "code";
86 86
87 87 var that = this;
88 88 this.element.focusout(
89 89 function() { that.auto_highlight(); }
90 90 );
91 91 };
92 92
93 93 CodeCell.options_default = {
94 94 cm_config : {
95 95 extraKeys: {
96 96 "Tab" : "indentMore",
97 97 "Shift-Tab" : "indentLess",
98 98 "Backspace" : "delSpaceToPrevTabStop",
99 99 "Cmd-/" : "toggleComment",
100 100 "Ctrl-/" : "toggleComment"
101 101 },
102 102 mode: 'ipython',
103 103 theme: 'ipython',
104 104 matchBrackets: true
105 105 }
106 106 };
107 107
108 108 CodeCell.msg_cells = {};
109 109
110 110 CodeCell.prototype = new IPython.Cell();
111 111
112 112 /**
113 113 * @method auto_highlight
114 114 */
115 115 CodeCell.prototype.auto_highlight = function () {
116 116 this._auto_highlight(IPython.config.cell_magic_highlight);
117 117 };
118 118
119 119 /** @method create_element */
120 120 CodeCell.prototype.create_element = function () {
121 121 IPython.Cell.prototype.create_element.apply(this, arguments);
122 122
123 123 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
124 124 cell.attr('tabindex','2');
125 125
126 126 var input = $('<div></div>').addClass('input');
127 127 var prompt = $('<div/>').addClass('prompt input_prompt');
128 128 var inner_cell = $('<div/>').addClass('inner_cell');
129 129 this.celltoolbar = new IPython.CellToolbar(this);
130 130 inner_cell.append(this.celltoolbar.element);
131 131 var input_area = $('<div/>').addClass('input_area');
132 132 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
133 133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
134 134 inner_cell.append(input_area);
135 135 input.append(prompt).append(inner_cell);
136 136
137 137 var widget_area = $('<div/>')
138 138 .addClass('widget-area')
139 139 .hide();
140 140 this.widget_area = widget_area;
141 141 var widget_prompt = $('<div/>')
142 142 .addClass('prompt')
143 143 .appendTo(widget_area);
144 144 var widget_subarea = $('<div/>')
145 145 .addClass('widget-subarea')
146 146 .appendTo(widget_area);
147 147 this.widget_subarea = widget_subarea;
148 148 var widget_clear_buton = $('<button />')
149 149 .addClass('close')
150 150 .html('&times;')
151 151 .click(function() {
152 152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
153 153 })
154 154 .appendTo(widget_prompt);
155 155
156 156 var output = $('<div></div>');
157 157 cell.append(input).append(widget_area).append(output);
158 158 this.element = cell;
159 159 this.output_area = new IPython.OutputArea(output, true);
160 160 this.completer = new IPython.Completer(this);
161 161 };
162 162
163 163 /** @method bind_events */
164 164 CodeCell.prototype.bind_events = function () {
165 165 IPython.Cell.prototype.bind_events.apply(this);
166 166 var that = this;
167 167
168 168 this.element.focusout(
169 169 function() { that.auto_highlight(); }
170 170 );
171 171 };
172 172
173 173 CodeCell.prototype.handle_keyevent = function (editor, event) {
174 174
175 175 // console.log('CM', this.mode, event.which, event.type)
176 176
177 177 if (this.mode === 'command') {
178 178 return true;
179 179 } else if (this.mode === 'edit') {
180 180 return this.handle_codemirror_keyevent(editor, event);
181 181 }
182 182 };
183 183
184 184 /**
185 185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
186 186 * handlers and is used to provide custom key handling. Its return
187 187 * value is used to determine if CodeMirror should ignore the event:
188 188 * true = ignore, false = don't ignore.
189 189 * @method handle_codemirror_keyevent
190 190 */
191 191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
192 192
193 193 var that = this;
194 194 // whatever key is pressed, first, cancel the tooltip request before
195 195 // they are sent, and remove tooltip if any, except for tab again
196 196 var tooltip_closed = null;
197 197 if (event.type === 'keydown' && event.which != key.TAB ) {
198 198 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
199 199 }
200 200
201 201 var cur = editor.getCursor();
202 202 if (event.keyCode === key.ENTER){
203 203 this.auto_highlight();
204 204 }
205 205
206 206 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
207 207 // Always ignore shift-enter in CodeMirror as we handle it.
208 208 return true;
209 209 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
210 210 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
211 211 // browser and keyboard layout !
212 212 // Pressing '(' , request tooltip, don't forget to reappend it
213 213 // The second argument says to hide the tooltip if the docstring
214 214 // is actually empty
215 215 IPython.tooltip.pending(that, true);
216 216 } else if (event.which === key.UPARROW && event.type === 'keydown') {
217 217 // If we are not at the top, let CM handle the up arrow and
218 218 // prevent the global keydown handler from handling it.
219 219 if (!that.at_top()) {
220 220 event.stop();
221 221 return false;
222 222 } else {
223 223 return true;
224 224 }
225 225 } else if (event.which === key.ESC && event.type === 'keydown') {
226 226 // First see if the tooltip is active and if so cancel it.
227 227 if (tooltip_closed) {
228 228 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
229 229 // force=true. Because of this it won't actually close the tooltip
230 230 // if it is in sticky mode. Thus, we have to check again if it is open
231 231 // and close it with force=true.
232 232 if (!IPython.tooltip._hidden) {
233 233 IPython.tooltip.remove_and_cancel_tooltip(true);
234 234 }
235 235 // If we closed the tooltip, don't let CM or the global handlers
236 236 // handle this event.
237 237 event.stop();
238 238 return true;
239 239 }
240 240 if (that.code_mirror.options.keyMap === "vim-insert") {
241 241 // vim keyMap is active and in insert mode. In this case we leave vim
242 242 // insert mode, but remain in notebook edit mode.
243 243 // Let' CM handle this event and prevent global handling.
244 244 event.stop();
245 245 return false;
246 246 } else {
247 247 // vim keyMap is not active. Leave notebook edit mode.
248 248 // Don't let CM handle the event, defer to global handling.
249 249 return true;
250 250 }
251 251 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
252 252 // If we are not at the bottom, let CM handle the down arrow and
253 253 // prevent the global keydown handler from handling it.
254 254 if (!that.at_bottom()) {
255 255 event.stop();
256 256 return false;
257 257 } else {
258 258 return true;
259 259 }
260 260 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
261 261 if (editor.somethingSelected()){
262 262 var anchor = editor.getCursor("anchor");
263 263 var head = editor.getCursor("head");
264 264 if( anchor.line != head.line){
265 265 return false;
266 266 }
267 267 }
268 268 IPython.tooltip.request(that);
269 269 event.stop();
270 270 return true;
271 271 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
272 272 // Tab completion.
273 273 IPython.tooltip.remove_and_cancel_tooltip();
274 274 if (editor.somethingSelected()) {
275 275 return false;
276 276 }
277 277 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
278 278 if (pre_cursor.trim() === "") {
279 279 // Don't autocomplete if the part of the line before the cursor
280 280 // is empty. In this case, let CodeMirror handle indentation.
281 281 return false;
282 282 } else {
283 283 event.stop();
284 284 this.completer.startCompletion();
285 285 return true;
286 286 }
287 287 } else {
288 288 // keypress/keyup also trigger on TAB press, and we don't want to
289 289 // use those to disable tab completion.
290 290 return false;
291 291 }
292 292 return false;
293 293 };
294 294
295 295 // Kernel related calls.
296 296
297 297 CodeCell.prototype.set_kernel = function (kernel) {
298 298 this.kernel = kernel;
299 299 };
300 300
301 301 /**
302 302 * Execute current code cell to the kernel
303 303 * @method execute
304 304 */
305 305 CodeCell.prototype.execute = function () {
306 306 this.output_area.clear_output();
307 307
308 308 // Clear widget area
309 309 this.widget_subarea.html('');
310 310 this.widget_subarea.height('');
311 311 this.widget_area.height('');
312 312 this.widget_area.hide();
313 313
314 314 this.set_input_prompt('*');
315 315 this.element.addClass("running");
316 316 if (this.last_msg_id) {
317 317 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
318 318 }
319 319 var callbacks = this.get_callbacks();
320 320
321 321 var old_msg_id = this.last_msg_id;
322 322 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
323 323 if (old_msg_id) {
324 324 delete CodeCell.msg_cells[old_msg_id];
325 325 }
326 326 CodeCell.msg_cells[this.last_msg_id] = this;
327 327 };
328 328
329 329 /**
330 330 * Construct the default callbacks for
331 331 * @method get_callbacks
332 332 */
333 333 CodeCell.prototype.get_callbacks = function () {
334 334 return {
335 335 shell : {
336 336 reply : $.proxy(this._handle_execute_reply, this),
337 337 payload : {
338 338 set_next_input : $.proxy(this._handle_set_next_input, this),
339 339 page : $.proxy(this._open_with_pager, this)
340 340 }
341 341 },
342 342 iopub : {
343 343 output : $.proxy(this.output_area.handle_output, this.output_area),
344 344 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
345 345 },
346 346 input : $.proxy(this._handle_input_request, this)
347 347 };
348 348 };
349 349
350 350 CodeCell.prototype._open_with_pager = function (payload) {
351 351 $([IPython.events]).trigger('open_with_text.Pager', payload);
352 352 };
353 353
354 354 /**
355 355 * @method _handle_execute_reply
356 356 * @private
357 357 */
358 358 CodeCell.prototype._handle_execute_reply = function (msg) {
359 359 this.set_input_prompt(msg.content.execution_count);
360 360 this.element.removeClass("running");
361 361 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
362 362 };
363 363
364 364 /**
365 365 * @method _handle_set_next_input
366 366 * @private
367 367 */
368 368 CodeCell.prototype._handle_set_next_input = function (payload) {
369 369 var data = {'cell': this, 'text': payload.text};
370 370 $([IPython.events]).trigger('set_next_input.Notebook', data);
371 371 };
372 372
373 373 /**
374 374 * @method _handle_input_request
375 375 * @private
376 376 */
377 377 CodeCell.prototype._handle_input_request = function (msg) {
378 378 this.output_area.append_raw_input(msg);
379 379 };
380 380
381 381
382 382 // Basic cell manipulation.
383 383
384 384 CodeCell.prototype.select = function () {
385 385 var cont = IPython.Cell.prototype.select.apply(this);
386 386 if (cont) {
387 387 this.code_mirror.refresh();
388 388 this.auto_highlight();
389 389 }
390 390 return cont;
391 391 };
392 392
393 393 CodeCell.prototype.render = function () {
394 394 var cont = IPython.Cell.prototype.render.apply(this);
395 395 // Always execute, even if we are already in the rendered state
396 396 return cont;
397 397 };
398 398
399 399 CodeCell.prototype.unrender = function () {
400 400 // CodeCell is always rendered
401 401 return false;
402 402 };
403 403
404 404 CodeCell.prototype.edit_mode = function () {
405 405 var cont = IPython.Cell.prototype.edit_mode.apply(this);
406 406 if (cont) {
407 407 this.focus_editor();
408 408 }
409 409 return cont;
410 410 }
411 411
412 412 CodeCell.prototype.select_all = function () {
413 413 var start = {line: 0, ch: 0};
414 414 var nlines = this.code_mirror.lineCount();
415 415 var last_line = this.code_mirror.getLine(nlines-1);
416 416 var end = {line: nlines-1, ch: last_line.length};
417 417 this.code_mirror.setSelection(start, end);
418 418 };
419 419
420 420
421 421 CodeCell.prototype.collapse_output = function () {
422 422 this.collapsed = true;
423 423 this.output_area.collapse();
424 424 };
425 425
426 426
427 427 CodeCell.prototype.expand_output = function () {
428 428 this.collapsed = false;
429 429 this.output_area.expand();
430 430 this.output_area.unscroll_area();
431 431 };
432 432
433 433 CodeCell.prototype.scroll_output = function () {
434 434 this.output_area.expand();
435 435 this.output_area.scroll_if_long();
436 436 };
437 437
438 438 CodeCell.prototype.toggle_output = function () {
439 439 this.collapsed = Boolean(1 - this.collapsed);
440 440 this.output_area.toggle_output();
441 441 };
442 442
443 443 CodeCell.prototype.toggle_output_scroll = function () {
444 444 this.output_area.toggle_scroll();
445 445 };
446 446
447 447
448 448 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
449 449 var ns;
450 450 if (prompt_value == undefined) {
451 451 ns = "&nbsp;";
452 452 } else {
453 453 ns = encodeURIComponent(prompt_value);
454 454 }
455 455 return 'In&nbsp;[' + ns + ']:';
456 456 };
457 457
458 458 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
459 459 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
460 460 for(var i=1; i < lines_number; i++) {
461 461 html.push(['...:']);
462 462 }
463 463 return html.join('<br/>');
464 464 };
465 465
466 466 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
467 467
468 468
469 469 CodeCell.prototype.set_input_prompt = function (number) {
470 470 var nline = 1;
471 471 if (this.code_mirror !== undefined) {
472 472 nline = this.code_mirror.lineCount();
473 473 }
474 474 this.input_prompt_number = number;
475 475 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
476 // This HTML call is okay because the user contents are escaped.
476 477 this.element.find('div.input_prompt').html(prompt_html);
477 478 };
478 479
479 480
480 481 CodeCell.prototype.clear_input = function () {
481 482 this.code_mirror.setValue('');
482 483 };
483 484
484 485
485 486 CodeCell.prototype.get_text = function () {
486 487 return this.code_mirror.getValue();
487 488 };
488 489
489 490
490 491 CodeCell.prototype.set_text = function (code) {
491 492 return this.code_mirror.setValue(code);
492 493 };
493 494
494 495
495 496 CodeCell.prototype.at_top = function () {
496 497 var cursor = this.code_mirror.getCursor();
497 498 if (cursor.line === 0 && cursor.ch === 0) {
498 499 return true;
499 500 } else {
500 501 return false;
501 502 }
502 503 };
503 504
504 505
505 506 CodeCell.prototype.at_bottom = function () {
506 507 var cursor = this.code_mirror.getCursor();
507 508 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
508 509 return true;
509 510 } else {
510 511 return false;
511 512 }
512 513 };
513 514
514 515
515 516 CodeCell.prototype.clear_output = function (wait) {
516 517 this.output_area.clear_output(wait);
517 518 this.set_input_prompt();
518 519 };
519 520
520 521
521 522 // JSON serialization
522 523
523 524 CodeCell.prototype.fromJSON = function (data) {
524 525 IPython.Cell.prototype.fromJSON.apply(this, arguments);
525 526 if (data.cell_type === 'code') {
526 527 if (data.input !== undefined) {
527 528 this.set_text(data.input);
528 529 // make this value the starting point, so that we can only undo
529 530 // to this state, instead of a blank cell
530 531 this.code_mirror.clearHistory();
531 532 this.auto_highlight();
532 533 }
533 534 if (data.prompt_number !== undefined) {
534 535 this.set_input_prompt(data.prompt_number);
535 536 } else {
536 537 this.set_input_prompt();
537 538 }
538 539 this.output_area.trusted = data.trusted || false;
539 540 this.output_area.fromJSON(data.outputs);
540 541 if (data.collapsed !== undefined) {
541 542 if (data.collapsed) {
542 543 this.collapse_output();
543 544 } else {
544 545 this.expand_output();
545 546 }
546 547 }
547 548 }
548 549 };
549 550
550 551
551 552 CodeCell.prototype.toJSON = function () {
552 553 var data = IPython.Cell.prototype.toJSON.apply(this);
553 554 data.input = this.get_text();
554 555 // is finite protect against undefined and '*' value
555 556 if (isFinite(this.input_prompt_number)) {
556 557 data.prompt_number = this.input_prompt_number;
557 558 }
558 559 var outputs = this.output_area.toJSON();
559 560 data.outputs = outputs;
560 561 data.language = 'python';
561 562 data.trusted = this.output_area.trusted;
562 563 data.collapsed = this.collapsed;
563 564 return data;
564 565 };
565 566
566 567
567 568 IPython.CodeCell = CodeCell;
568 569
569 570 return IPython;
570 571 }(IPython));
@@ -1,866 +1,869 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // OutputArea
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule OutputArea
16 16 */
17 17 var IPython = (function (IPython) {
18 18 "use strict";
19 19
20 20 var utils = IPython.utils;
21 21
22 22 /**
23 23 * @class OutputArea
24 24 *
25 25 * @constructor
26 26 */
27 27
28 28 var OutputArea = function (selector, prompt_area) {
29 29 this.selector = selector;
30 30 this.wrapper = $(selector);
31 31 this.outputs = [];
32 32 this.collapsed = false;
33 33 this.scrolled = false;
34 34 this.trusted = true;
35 35 this.clear_queued = null;
36 36 if (prompt_area === undefined) {
37 37 this.prompt_area = true;
38 38 } else {
39 39 this.prompt_area = prompt_area;
40 40 }
41 41 this.create_elements();
42 42 this.style();
43 43 this.bind_events();
44 44 };
45 45
46 46
47 47 /**
48 48 * Class prototypes
49 49 **/
50 50
51 51 OutputArea.prototype.create_elements = function () {
52 52 this.element = $("<div/>");
53 53 this.collapse_button = $("<div/>");
54 54 this.prompt_overlay = $("<div/>");
55 55 this.wrapper.append(this.prompt_overlay);
56 56 this.wrapper.append(this.element);
57 57 this.wrapper.append(this.collapse_button);
58 58 };
59 59
60 60
61 61 OutputArea.prototype.style = function () {
62 62 this.collapse_button.hide();
63 63 this.prompt_overlay.hide();
64 64
65 65 this.wrapper.addClass('output_wrapper');
66 66 this.element.addClass('output');
67 67
68 68 this.collapse_button.addClass("btn output_collapsed");
69 69 this.collapse_button.attr('title', 'click to expand output');
70 70 this.collapse_button.text('. . .');
71 71
72 72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74 74
75 75 this.collapse();
76 76 };
77 77
78 78 /**
79 79 * Should the OutputArea scroll?
80 80 * Returns whether the height (in lines) exceeds a threshold.
81 81 *
82 82 * @private
83 83 * @method _should_scroll
84 84 * @param [lines=100]{Integer}
85 85 * @return {Bool}
86 86 *
87 87 */
88 88 OutputArea.prototype._should_scroll = function (lines) {
89 89 if (lines <=0 ){ return }
90 90 if (!lines) {
91 91 lines = 100;
92 92 }
93 93 // line-height from http://stackoverflow.com/questions/1185151
94 94 var fontSize = this.element.css('font-size');
95 95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96 96
97 97 return (this.element.height() > lines * lineHeight);
98 98 };
99 99
100 100
101 101 OutputArea.prototype.bind_events = function () {
102 102 var that = this;
103 103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105 105
106 106 this.element.resize(function () {
107 107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 109 return;
110 110 }
111 111 // maybe scroll output,
112 112 // if it's grown large enough and hasn't already been scrolled.
113 113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 114 that.scroll_area();
115 115 }
116 116 });
117 117 this.collapse_button.click(function () {
118 118 that.expand();
119 119 });
120 120 };
121 121
122 122
123 123 OutputArea.prototype.collapse = function () {
124 124 if (!this.collapsed) {
125 125 this.element.hide();
126 126 this.prompt_overlay.hide();
127 127 if (this.element.html()){
128 128 this.collapse_button.show();
129 129 }
130 130 this.collapsed = true;
131 131 }
132 132 };
133 133
134 134
135 135 OutputArea.prototype.expand = function () {
136 136 if (this.collapsed) {
137 137 this.collapse_button.hide();
138 138 this.element.show();
139 139 this.prompt_overlay.show();
140 140 this.collapsed = false;
141 141 }
142 142 };
143 143
144 144
145 145 OutputArea.prototype.toggle_output = function () {
146 146 if (this.collapsed) {
147 147 this.expand();
148 148 } else {
149 149 this.collapse();
150 150 }
151 151 };
152 152
153 153
154 154 OutputArea.prototype.scroll_area = function () {
155 155 this.element.addClass('output_scroll');
156 156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 157 this.scrolled = true;
158 158 };
159 159
160 160
161 161 OutputArea.prototype.unscroll_area = function () {
162 162 this.element.removeClass('output_scroll');
163 163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 164 this.scrolled = false;
165 165 };
166 166
167 167 /**
168 168 *
169 169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 170 *
171 171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 172 * OutputArea.minimum_scroll_threshold.
173 173 *
174 174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 175 *
176 176 * @method scroll_if_long
177 177 *
178 178 * @param [lines=20]{Number} Default to 20 if not set,
179 179 * behavior undefined for value of `0`.
180 180 *
181 181 **/
182 182 OutputArea.prototype.scroll_if_long = function (lines) {
183 183 var n = lines | OutputArea.minimum_scroll_threshold;
184 184 if(n <= 0){
185 185 return
186 186 }
187 187
188 188 if (this._should_scroll(n)) {
189 189 // only allow scrolling long-enough output
190 190 this.scroll_area();
191 191 }
192 192 };
193 193
194 194
195 195 OutputArea.prototype.toggle_scroll = function () {
196 196 if (this.scrolled) {
197 197 this.unscroll_area();
198 198 } else {
199 199 // only allow scrolling long-enough output
200 200 this.scroll_if_long();
201 201 }
202 202 };
203 203
204 204
205 205 // typeset with MathJax if MathJax is available
206 206 OutputArea.prototype.typeset = function () {
207 207 if (window.MathJax){
208 208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 209 }
210 210 };
211 211
212 212
213 213 OutputArea.prototype.handle_output = function (msg) {
214 214 var json = {};
215 215 var msg_type = json.output_type = msg.header.msg_type;
216 216 var content = msg.content;
217 217 if (msg_type === "stream") {
218 218 json.text = content.data;
219 219 json.stream = content.name;
220 220 } else if (msg_type === "display_data") {
221 221 json = content.data;
222 222 json.output_type = msg_type;
223 223 json.metadata = content.metadata;
224 224 } else if (msg_type === "pyout") {
225 225 json = content.data;
226 226 json.output_type = msg_type;
227 227 json.metadata = content.metadata;
228 228 json.prompt_number = content.execution_count;
229 229 } else if (msg_type === "pyerr") {
230 230 json.ename = content.ename;
231 231 json.evalue = content.evalue;
232 232 json.traceback = content.traceback;
233 233 }
234 234 this.append_output(json);
235 235 };
236 236
237 237
238 238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 239 var remapped = {};
240 240 for (var key in data) {
241 241 var new_key = key_map[key] || key;
242 242 remapped[new_key] = data[key];
243 243 }
244 244 return remapped;
245 245 };
246 246
247 247
248 248 OutputArea.output_types = [
249 249 'application/javascript',
250 250 'text/html',
251 251 'text/latex',
252 252 'image/svg+xml',
253 253 'image/png',
254 254 'image/jpeg',
255 255 'application/pdf',
256 256 'text/plain'
257 257 ];
258 258
259 259 OutputArea.prototype.validate_output = function (json) {
260 260 // scrub invalid outputs
261 261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 262 // nbformat 4 will fix that.
263 263 $.map(OutputArea.output_types, function(key){
264 264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 265 console.log("Invalid type for " + key, json[key]);
266 266 delete json[key];
267 267 }
268 268 });
269 269 return json;
270 270 };
271 271
272 272 OutputArea.prototype.append_output = function (json) {
273 273 this.expand();
274 274 // Clear the output if clear is queued.
275 275 var needs_height_reset = false;
276 276 if (this.clear_queued) {
277 277 this.clear_output(false);
278 278 needs_height_reset = true;
279 279 }
280 280
281 281 // validate output data types
282 282 json = this.validate_output(json);
283 283
284 284 if (json.output_type === 'pyout') {
285 285 this.append_pyout(json);
286 286 } else if (json.output_type === 'pyerr') {
287 287 this.append_pyerr(json);
288 288 } else if (json.output_type === 'display_data') {
289 289 this.append_display_data(json);
290 290 } else if (json.output_type === 'stream') {
291 291 this.append_stream(json);
292 292 }
293 293
294 294 this.outputs.push(json);
295 295
296 296 // Only reset the height to automatic if the height is currently
297 297 // fixed (done by wait=True flag on clear_output).
298 298 if (needs_height_reset) {
299 299 this.element.height('');
300 300 }
301 301
302 302 var that = this;
303 303 setTimeout(function(){that.element.trigger('resize');}, 100);
304 304 };
305 305
306 306
307 307 OutputArea.prototype.create_output_area = function () {
308 308 var oa = $("<div/>").addClass("output_area");
309 309 if (this.prompt_area) {
310 310 oa.append($('<div/>').addClass('prompt'));
311 311 }
312 312 return oa;
313 313 };
314 314
315 315
316 316 function _get_metadata_key(metadata, key, mime) {
317 317 var mime_md = metadata[mime];
318 318 // mime-specific higher priority
319 319 if (mime_md && mime_md[key] !== undefined) {
320 320 return mime_md[key];
321 321 }
322 322 // fallback on global
323 323 return metadata[key];
324 324 }
325 325
326 326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
327 327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
328 328 if (_get_metadata_key(md, 'isolated', mime)) {
329 329 // Create an iframe to isolate the subarea from the rest of the
330 330 // document
331 331 var iframe = $('<iframe/>').addClass('box-flex1');
332 332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
333 333 iframe.attr('frameborder', 0);
334 334 iframe.attr('scrolling', 'auto');
335 335
336 336 // Once the iframe is loaded, the subarea is dynamically inserted
337 337 iframe.on('load', function() {
338 338 // Workaround needed by Firefox, to properly render svg inside
339 339 // iframes, see http://stackoverflow.com/questions/10177190/
340 340 // svg-dynamically-added-to-iframe-does-not-render-correctly
341 341 this.contentDocument.open();
342 342
343 343 // Insert the subarea into the iframe
344 344 // We must directly write the html. When using Jquery's append
345 345 // method, javascript is evaluated in the parent document and
346 // not in the iframe document.
346 // not in the iframe document. At this point, subarea doesn't
347 // contain any user content.
347 348 this.contentDocument.write(subarea.html());
348 349
349 350 this.contentDocument.close();
350 351
351 352 var body = this.contentDocument.body;
352 353 // Adjust the iframe height automatically
353 354 iframe.height(body.scrollHeight + 'px');
354 355 });
355 356
356 357 // Elements should be appended to the inner subarea and not to the
357 358 // iframe
358 359 iframe.append = function(that) {
359 360 subarea.append(that);
360 361 };
361 362
362 363 return iframe;
363 364 } else {
364 365 return subarea;
365 366 }
366 367 }
367 368
368 369
369 370 OutputArea.prototype._append_javascript_error = function (err, element) {
370 371 // display a message when a javascript error occurs in display output
371 372 var msg = "Javascript error adding output!"
372 373 if ( element === undefined ) return;
373 element.append(
374 $('<div/>').html(msg + "<br/>" +
375 err.toString() +
376 '<br/>See your browser Javascript console for more details.'
377 ).addClass('js-error')
378 );
374 element
375 .append($('<div/>').text(msg).addClass('js-error'))
376 .append($('<div/>').text(err.toString()).addClass('js-error'))
377 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
379 378 };
380 379
381 380 OutputArea.prototype._safe_append = function (toinsert) {
382 381 // safely append an item to the document
383 382 // this is an object created by user code,
384 383 // and may have errors, which should not be raised
385 384 // under any circumstances.
386 385 try {
387 386 this.element.append(toinsert);
388 387 } catch(err) {
389 388 console.log(err);
390 389 // Create an actual output_area and output_subarea, which creates
391 390 // the prompt area and the proper indentation.
392 391 var toinsert = this.create_output_area();
393 392 var subarea = $('<div/>').addClass('output_subarea');
394 393 toinsert.append(subarea);
395 394 this._append_javascript_error(err, subarea);
396 395 this.element.append(toinsert);
397 396 }
398 397 };
399 398
400 399
401 400 OutputArea.prototype.append_pyout = function (json) {
402 401 var n = json.prompt_number || ' ';
403 402 var toinsert = this.create_output_area();
404 403 if (this.prompt_area) {
405 404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
406 405 }
407 406 this.append_mime_type(json, toinsert);
408 407 this._safe_append(toinsert);
409 408 // If we just output latex, typeset it.
410 409 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
411 410 this.typeset();
412 411 }
413 412 };
414 413
415 414
416 415 OutputArea.prototype.append_pyerr = function (json) {
417 416 var tb = json.traceback;
418 417 if (tb !== undefined && tb.length > 0) {
419 418 var s = '';
420 419 var len = tb.length;
421 420 for (var i=0; i<len; i++) {
422 421 s = s + tb[i] + '\n';
423 422 }
424 423 s = s + '\n';
425 424 var toinsert = this.create_output_area();
426 425 this.append_text(s, {}, toinsert);
427 426 this._safe_append(toinsert);
428 427 }
429 428 };
430 429
431 430
432 431 OutputArea.prototype.append_stream = function (json) {
433 432 // temporary fix: if stream undefined (json file written prior to this patch),
434 433 // default to most likely stdout:
435 434 if (json.stream == undefined){
436 435 json.stream = 'stdout';
437 436 }
438 437 var text = json.text;
439 438 var subclass = "output_"+json.stream;
440 439 if (this.outputs.length > 0){
441 440 // have at least one output to consider
442 441 var last = this.outputs[this.outputs.length-1];
443 442 if (last.output_type == 'stream' && json.stream == last.stream){
444 443 // latest output was in the same stream,
445 444 // so append directly into its pre tag
446 445 // escape ANSI & HTML specials:
447 446 var pre = this.element.find('div.'+subclass).last().find('pre');
448 447 var html = utils.fixCarriageReturn(
449 448 pre.html() + utils.fixConsole(text));
449 // The only user content injected with with this HTML call is
450 // escaped by the fixConsole() method.
450 451 pre.html(html);
451 452 return;
452 453 }
453 454 }
454 455
455 456 if (!text.replace("\r", "")) {
456 457 // text is nothing (empty string, \r, etc.)
457 458 // so don't append any elements, which might add undesirable space
458 459 return;
459 460 }
460 461
461 462 // If we got here, attach a new div
462 463 var toinsert = this.create_output_area();
463 464 this.append_text(text, {}, toinsert, "output_stream "+subclass);
464 465 this._safe_append(toinsert);
465 466 };
466 467
467 468
468 469 OutputArea.prototype.append_display_data = function (json) {
469 470 var toinsert = this.create_output_area();
470 471 if (this.append_mime_type(json, toinsert)) {
471 472 this._safe_append(toinsert);
472 473 // If we just output latex, typeset it.
473 474 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
474 475 this.typeset();
475 476 }
476 477 }
477 478 };
478 479
479 480
480 481 OutputArea.safe_outputs = {
481 482 'text/plain' : true,
482 483 'image/png' : true,
483 484 'image/jpeg' : true
484 485 };
485 486
486 487 OutputArea.prototype.append_mime_type = function (json, element) {
487 488 for (var type_i in OutputArea.display_order) {
488 489 var type = OutputArea.display_order[type_i];
489 490 var append = OutputArea.append_map[type];
490 491 if ((json[type] !== undefined) && append) {
491 492 if (!this.trusted && !OutputArea.safe_outputs[type]) {
492 493 // not trusted show warning and do not display
493 494 var content = {
494 495 text : "Untrusted " + type + " output ignored.",
495 496 stream : "stderr"
496 497 }
497 498 this.append_stream(content);
498 499 continue;
499 500 }
500 501 var md = json.metadata || {};
501 502 var toinsert = append.apply(this, [json[type], md, element]);
502 503 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
503 504 return true;
504 505 }
505 506 }
506 507 return false;
507 508 };
508 509
509 510
510 511 OutputArea.prototype.append_html = function (html, md, element) {
511 512 var type = 'text/html';
512 513 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
513 514 IPython.keyboard_manager.register_events(toinsert);
514 515 toinsert.append(html);
515 516 element.append(toinsert);
516 517 return toinsert;
517 518 };
518 519
519 520
520 521 OutputArea.prototype.append_javascript = function (js, md, element) {
521 522 // We just eval the JS code, element appears in the local scope.
522 523 var type = 'application/javascript';
523 524 var toinsert = this.create_output_subarea(md, "output_javascript", type);
524 525 IPython.keyboard_manager.register_events(toinsert);
525 526 element.append(toinsert);
526 527 // FIXME TODO : remove `container element for 3.0`
527 528 //backward compat, js should be eval'ed in a context where `container` is defined.
528 529 var container = element;
529 530 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
530 531 // end backward compat
531 532 try {
532 533 eval(js);
533 534 } catch(err) {
534 535 console.log(err);
535 536 this._append_javascript_error(err, toinsert);
536 537 }
537 538 return toinsert;
538 539 };
539 540
540 541
541 542 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
542 543 var type = 'text/plain';
543 544 var toinsert = this.create_output_subarea(md, "output_text", type);
544 545 // escape ANSI & HTML specials in plaintext:
545 546 data = utils.fixConsole(data);
546 547 data = utils.fixCarriageReturn(data);
547 548 data = utils.autoLinkUrls(data);
548 549 if (extra_class){
549 550 toinsert.addClass(extra_class);
550 551 }
552 // The only user content injected with with this HTML call is
553 // escaped by the fixConsole() method.
551 554 toinsert.append($("<pre/>").html(data));
552 555 element.append(toinsert);
553 556 return toinsert;
554 557 };
555 558
556 559
557 560 OutputArea.prototype.append_svg = function (svg, md, element) {
558 561 var type = 'image/svg+xml';
559 562 var toinsert = this.create_output_subarea(md, "output_svg", type);
560 563 toinsert.append(svg);
561 564 element.append(toinsert);
562 565 return toinsert;
563 566 };
564 567
565 568
566 569 OutputArea.prototype._dblclick_to_reset_size = function (img) {
567 570 // schedule wrapping image in resizable after a delay,
568 571 // so we don't end up calling resize on a zero-size object
569 572 var that = this;
570 573 setTimeout(function () {
571 574 var h0 = img.height();
572 575 var w0 = img.width();
573 576 if (!(h0 && w0)) {
574 577 // zero size, schedule another timeout
575 578 that._dblclick_to_reset_size(img);
576 579 return;
577 580 }
578 581 img.resizable({
579 582 aspectRatio: true,
580 583 autoHide: true
581 584 });
582 585 img.dblclick(function () {
583 586 // resize wrapper & image together for some reason:
584 587 img.parent().height(h0);
585 588 img.height(h0);
586 589 img.parent().width(w0);
587 590 img.width(w0);
588 591 });
589 592 }, 250);
590 593 };
591 594
592 595 var set_width_height = function (img, md, mime) {
593 596 // set width and height of an img element from metadata
594 597 var height = _get_metadata_key(md, 'height', mime);
595 598 if (height !== undefined) img.attr('height', height);
596 599 var width = _get_metadata_key(md, 'width', mime);
597 600 if (width !== undefined) img.attr('width', width);
598 601 };
599 602
600 603 OutputArea.prototype.append_png = function (png, md, element) {
601 604 var type = 'image/png';
602 605 var toinsert = this.create_output_subarea(md, "output_png", type);
603 606 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
604 607 set_width_height(img, md, 'image/png');
605 608 this._dblclick_to_reset_size(img);
606 609 toinsert.append(img);
607 610 element.append(toinsert);
608 611 return toinsert;
609 612 };
610 613
611 614
612 615 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
613 616 var type = 'image/jpeg';
614 617 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
615 618 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
616 619 set_width_height(img, md, 'image/jpeg');
617 620 this._dblclick_to_reset_size(img);
618 621 toinsert.append(img);
619 622 element.append(toinsert);
620 623 return toinsert;
621 624 };
622 625
623 626
624 627 OutputArea.prototype.append_pdf = function (pdf, md, element) {
625 628 var type = 'application/pdf';
626 629 var toinsert = this.create_output_subarea(md, "output_pdf", type);
627 630 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
628 631 a.attr('target', '_blank');
629 632 a.text('View PDF')
630 633 toinsert.append(a);
631 634 element.append(toinsert);
632 635 return toinsert;
633 636 }
634 637
635 638 OutputArea.prototype.append_latex = function (latex, md, element) {
636 639 // This method cannot do the typesetting because the latex first has to
637 640 // be on the page.
638 641 var type = 'text/latex';
639 642 var toinsert = this.create_output_subarea(md, "output_latex", type);
640 643 toinsert.append(latex);
641 644 element.append(toinsert);
642 645 return toinsert;
643 646 };
644 647
645 648
646 649 OutputArea.prototype.append_raw_input = function (msg) {
647 650 var that = this;
648 651 this.expand();
649 652 var content = msg.content;
650 653 var area = this.create_output_area();
651 654
652 655 // disable any other raw_inputs, if they are left around
653 656 $("div.output_subarea.raw_input").remove();
654 657
655 658 area.append(
656 659 $("<div/>")
657 660 .addClass("box-flex1 output_subarea raw_input")
658 661 .append(
659 662 $("<span/>")
660 663 .addClass("input_prompt")
661 664 .text(content.prompt)
662 665 )
663 666 .append(
664 667 $("<input/>")
665 668 .addClass("raw_input")
666 669 .attr('type', 'text')
667 670 .attr("size", 47)
668 671 .keydown(function (event, ui) {
669 672 // make sure we submit on enter,
670 673 // and don't re-execute the *cell* on shift-enter
671 674 if (event.which === utils.keycodes.ENTER) {
672 675 that._submit_raw_input();
673 676 return false;
674 677 }
675 678 })
676 679 )
677 680 );
678 681
679 682 this.element.append(area);
680 683 var raw_input = area.find('input.raw_input');
681 684 // Register events that enable/disable the keyboard manager while raw
682 685 // input is focused.
683 686 IPython.keyboard_manager.register_events(raw_input);
684 687 // Note, the following line used to read raw_input.focus().focus().
685 688 // This seemed to be needed otherwise only the cell would be focused.
686 689 // But with the modal UI, this seems to work fine with one call to focus().
687 690 raw_input.focus();
688 691 }
689 692
690 693 OutputArea.prototype._submit_raw_input = function (evt) {
691 694 var container = this.element.find("div.raw_input");
692 695 var theprompt = container.find("span.input_prompt");
693 696 var theinput = container.find("input.raw_input");
694 697 var value = theinput.val();
695 698 var content = {
696 699 output_type : 'stream',
697 700 name : 'stdout',
698 701 text : theprompt.text() + value + '\n'
699 702 }
700 703 // remove form container
701 704 container.parent().remove();
702 705 // replace with plaintext version in stdout
703 706 this.append_output(content, false);
704 707 $([IPython.events]).trigger('send_input_reply.Kernel', value);
705 708 }
706 709
707 710
708 711 OutputArea.prototype.handle_clear_output = function (msg) {
709 712 // msg spec v4 had stdout, stderr, display keys
710 713 // v4.1 replaced these with just wait
711 714 // The default behavior is the same (stdout=stderr=display=True, wait=False),
712 715 // so v4 messages will still be properly handled,
713 716 // except for the rarely used clearing less than all output.
714 717 this.clear_output(msg.content.wait || false);
715 718 };
716 719
717 720
718 721 OutputArea.prototype.clear_output = function(wait) {
719 722 if (wait) {
720 723
721 724 // If a clear is queued, clear before adding another to the queue.
722 725 if (this.clear_queued) {
723 726 this.clear_output(false);
724 727 };
725 728
726 729 this.clear_queued = true;
727 730 } else {
728 731
729 732 // Fix the output div's height if the clear_output is waiting for
730 733 // new output (it is being used in an animation).
731 734 if (this.clear_queued) {
732 735 var height = this.element.height();
733 736 this.element.height(height);
734 737 this.clear_queued = false;
735 738 }
736 739
737 740 // clear all, no need for logic
738 741 this.element.html("");
739 742 this.outputs = [];
740 743 this.trusted = true;
741 744 this.unscroll_area();
742 745 return;
743 746 };
744 747 };
745 748
746 749
747 750 // JSON serialization
748 751
749 752 OutputArea.prototype.fromJSON = function (outputs) {
750 753 var len = outputs.length;
751 754 var data;
752 755
753 756 for (var i=0; i<len; i++) {
754 757 data = outputs[i];
755 758 var msg_type = data.output_type;
756 759 if (msg_type === "display_data" || msg_type === "pyout") {
757 760 // convert short keys to mime keys
758 761 // TODO: remove mapping of short keys when we update to nbformat 4
759 762 data = this.rename_keys(data, OutputArea.mime_map_r);
760 763 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
761 764 }
762 765
763 766 this.append_output(data);
764 767 }
765 768 };
766 769
767 770
768 771 OutputArea.prototype.toJSON = function () {
769 772 var outputs = [];
770 773 var len = this.outputs.length;
771 774 var data;
772 775 for (var i=0; i<len; i++) {
773 776 data = this.outputs[i];
774 777 var msg_type = data.output_type;
775 778 if (msg_type === "display_data" || msg_type === "pyout") {
776 779 // convert mime keys to short keys
777 780 data = this.rename_keys(data, OutputArea.mime_map);
778 781 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
779 782 }
780 783 outputs[i] = data;
781 784 }
782 785 return outputs;
783 786 };
784 787
785 788 /**
786 789 * Class properties
787 790 **/
788 791
789 792 /**
790 793 * Threshold to trigger autoscroll when the OutputArea is resized,
791 794 * typically when new outputs are added.
792 795 *
793 796 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
794 797 * unless it is < 0, in which case autoscroll will never be triggered
795 798 *
796 799 * @property auto_scroll_threshold
797 800 * @type Number
798 801 * @default 100
799 802 *
800 803 **/
801 804 OutputArea.auto_scroll_threshold = 100;
802 805
803 806 /**
804 807 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
805 808 * shorter than this are never scrolled.
806 809 *
807 810 * @property minimum_scroll_threshold
808 811 * @type Number
809 812 * @default 20
810 813 *
811 814 **/
812 815 OutputArea.minimum_scroll_threshold = 20;
813 816
814 817
815 818
816 819 OutputArea.mime_map = {
817 820 "text/plain" : "text",
818 821 "text/html" : "html",
819 822 "image/svg+xml" : "svg",
820 823 "image/png" : "png",
821 824 "image/jpeg" : "jpeg",
822 825 "application/pdf" : "pdf",
823 826 "text/latex" : "latex",
824 827 "application/json" : "json",
825 828 "application/javascript" : "javascript",
826 829 };
827 830
828 831 OutputArea.mime_map_r = {
829 832 "text" : "text/plain",
830 833 "html" : "text/html",
831 834 "svg" : "image/svg+xml",
832 835 "png" : "image/png",
833 836 "jpeg" : "image/jpeg",
834 837 "pdf" : "application/pdf",
835 838 "latex" : "text/latex",
836 839 "json" : "application/json",
837 840 "javascript" : "application/javascript",
838 841 };
839 842
840 843 OutputArea.display_order = [
841 844 'application/javascript',
842 845 'text/html',
843 846 'text/latex',
844 847 'image/svg+xml',
845 848 'image/png',
846 849 'image/jpeg',
847 850 'application/pdf',
848 851 'text/plain'
849 852 ];
850 853
851 854 OutputArea.append_map = {
852 855 "text/plain" : OutputArea.prototype.append_text,
853 856 "text/html" : OutputArea.prototype.append_html,
854 857 "image/svg+xml" : OutputArea.prototype.append_svg,
855 858 "image/png" : OutputArea.prototype.append_png,
856 859 "image/jpeg" : OutputArea.prototype.append_jpeg,
857 860 "text/latex" : OutputArea.prototype.append_latex,
858 861 "application/javascript" : OutputArea.prototype.append_javascript,
859 862 "application/pdf" : OutputArea.prototype.append_pdf
860 863 };
861 864
862 865 IPython.OutputArea = OutputArea;
863 866
864 867 return IPython;
865 868
866 869 }(IPython));
@@ -1,176 +1,178 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Pager
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16
17 17 var Pager = function (pager_selector, pager_splitter_selector) {
18 18 this.pager_element = $(pager_selector);
19 19 this.pager_button_area = $('#pager_button_area');
20 20 var that = this;
21 21 this.percentage_height = 0.40;
22 22 this.pager_splitter_element = $(pager_splitter_selector)
23 23 .draggable({
24 24 containment: 'window',
25 25 axis:'y',
26 26 helper: null ,
27 27 drag: function(event, ui) {
28 28 // recalculate the amount of space the pager should take
29 29 var pheight = ($(document.body).height()-event.clientY-4);
30 30 var downprct = pheight/IPython.layout_manager.app_height();
31 31 downprct = Math.min(0.9, downprct);
32 32 if (downprct < 0.1) {
33 33 that.percentage_height = 0.1;
34 34 that.collapse({'duration':0});
35 35 } else if (downprct > 0.2) {
36 36 that.percentage_height = downprct;
37 37 that.expand({'duration':0});
38 38 }
39 39 IPython.layout_manager.do_resize();
40 40 }
41 41 });
42 42 this.expanded = false;
43 43 this.style();
44 44 this.create_button_area();
45 45 this.bind_events();
46 46 };
47 47
48 48 Pager.prototype.create_button_area = function(){
49 49 var that = this;
50 50 this.pager_button_area.append(
51 51 $('<a>').attr('role', "button")
52 52 .attr('title',"Open the pager in an external window")
53 53 .addClass('ui-button')
54 54 .click(function(){that.detach()})
55 55 .attr('style','position: absolute; right: 20px;')
56 56 .append(
57 57 $('<span>').addClass("ui-icon ui-icon-extlink")
58 58 )
59 59 )
60 60 this.pager_button_area.append(
61 61 $('<a>').attr('role', "button")
62 62 .attr('title',"Close the pager")
63 63 .addClass('ui-button')
64 64 .click(function(){that.collapse()})
65 65 .attr('style','position: absolute; right: 5px;')
66 66 .append(
67 67 $('<span>').addClass("ui-icon ui-icon-close")
68 68 )
69 69 )
70 70 };
71 71
72 72 Pager.prototype.style = function () {
73 73 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
74 74 this.pager_element.addClass('border-box-sizing');
75 75 this.pager_element.find(".container").addClass('border-box-sizing');
76 76 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
77 77 };
78 78
79 79
80 80 Pager.prototype.bind_events = function () {
81 81 var that = this;
82 82
83 83 this.pager_element.bind('collapse_pager', function (event, extrap) {
84 84 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
85 85 that.pager_element.hide(time);
86 86 });
87 87
88 88 this.pager_element.bind('expand_pager', function (event, extrap) {
89 89 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
90 90 that.pager_element.show(time);
91 91 });
92 92
93 93 this.pager_splitter_element.hover(
94 94 function () {
95 95 that.pager_splitter_element.addClass('ui-state-hover');
96 96 },
97 97 function () {
98 98 that.pager_splitter_element.removeClass('ui-state-hover');
99 99 }
100 100 );
101 101
102 102 this.pager_splitter_element.click(function () {
103 103 that.toggle();
104 104 });
105 105
106 106 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
107 107 if (data.text.trim() !== '') {
108 108 that.clear();
109 109 that.expand();
110 110 that.append_text(data.text);
111 111 };
112 112 });
113 113 };
114 114
115 115
116 116 Pager.prototype.collapse = function (extrap) {
117 117 if (this.expanded === true) {
118 118 this.expanded = false;
119 119 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
120 120 };
121 121 };
122 122
123 123
124 124 Pager.prototype.expand = function (extrap) {
125 125 if (this.expanded !== true) {
126 126 this.expanded = true;
127 127 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
128 128 };
129 129 };
130 130
131 131
132 132 Pager.prototype.toggle = function () {
133 133 if (this.expanded === true) {
134 134 this.collapse();
135 135 } else {
136 136 this.expand();
137 137 };
138 138 };
139 139
140 140
141 141 Pager.prototype.clear = function (text) {
142 142 this.pager_element.find(".container").empty();
143 143 };
144 144
145 145 Pager.prototype.detach = function(){
146 146 var w = window.open("","_blank");
147 147 $(w.document.head)
148 148 .append(
149 149 $('<link>')
150 150 .attr('rel',"stylesheet")
151 151 .attr('href',"/static/css/notebook.css")
152 152 .attr('type',"text/css")
153 153 )
154 154 .append(
155 155 $('<title>').text("IPython Pager")
156 156 );
157 157 var pager_body = $(w.document.body);
158 158 pager_body.css('overflow','scroll');
159 159
160 160 pager_body.append(this.pager_element.clone().children());
161 161 w.document.close();
162 162 this.collapse();
163 163
164 164 }
165 165
166 166 Pager.prototype.append_text = function (text) {
167 // The only user content injected with with this HTML call is escaped by
168 // the fixConsole() method.
167 169 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
168 170 };
169 171
170 172
171 173 IPython.Pager = Pager;
172 174
173 175 return IPython;
174 176
175 177 }(IPython));
176 178
@@ -1,560 +1,562 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // TextCell
10 10 //============================================================================
11 11
12 12
13 13
14 14 /**
15 15 A module that allow to create different type of Text Cell
16 16 @module IPython
17 17 @namespace IPython
18 18 */
19 19 var IPython = (function (IPython) {
20 20 "use strict";
21 21
22 22 // TextCell base class
23 23 var key = IPython.utils.keycodes;
24 24
25 25 /**
26 26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 27 * cell start as not redered.
28 28 *
29 29 * @class TextCell
30 30 * @constructor TextCell
31 31 * @extend IPython.Cell
32 32 * @param {object|undefined} [options]
33 33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 35 */
36 36 var TextCell = function (options) {
37 37 // in all TextCell/Cell subclasses
38 38 // do not assign most of members here, just pass it down
39 39 // in the options dict potentially overwriting what you wish.
40 40 // they will be assigned in the base class.
41 41
42 42 // we cannot put this as a class key as it has handle to "this".
43 43 var cm_overwrite_options = {
44 44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 45 };
46 46
47 47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48 48
49 49 this.cell_type = this.cell_type || 'text';
50 50
51 51 IPython.Cell.apply(this, [options]);
52 52
53 53 this.rendered = false;
54 54 };
55 55
56 56 TextCell.prototype = new IPython.Cell();
57 57
58 58 TextCell.options_default = {
59 59 cm_config : {
60 60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 61 mode: 'htmlmixed',
62 62 lineWrapping : true,
63 63 }
64 64 };
65 65
66 66
67 67 /**
68 68 * Create the DOM element of the TextCell
69 69 * @method create_element
70 70 * @private
71 71 */
72 72 TextCell.prototype.create_element = function () {
73 73 IPython.Cell.prototype.create_element.apply(this, arguments);
74 74
75 75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 76 cell.attr('tabindex','2');
77 77
78 78 var prompt = $('<div/>').addClass('prompt input_prompt');
79 79 cell.append(prompt);
80 80 var inner_cell = $('<div/>').addClass('inner_cell');
81 81 this.celltoolbar = new IPython.CellToolbar(this);
82 82 inner_cell.append(this.celltoolbar.element);
83 83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
84 84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
85 85 // The tabindex=-1 makes this div focusable.
86 86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 87 addClass('rendered_html').attr('tabindex','-1');
88 88 inner_cell.append(input_area).append(render_area);
89 89 cell.append(inner_cell);
90 90 this.element = cell;
91 91 };
92 92
93 93
94 94 /**
95 95 * Bind the DOM evet to cell actions
96 96 * Need to be called after TextCell.create_element
97 97 * @private
98 98 * @method bind_event
99 99 */
100 100 TextCell.prototype.bind_events = function () {
101 101 IPython.Cell.prototype.bind_events.apply(this);
102 102 var that = this;
103 103
104 104 this.element.dblclick(function () {
105 105 if (that.selected === false) {
106 106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 107 };
108 108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
109 109 });
110 110 };
111 111
112 112 TextCell.prototype.handle_keyevent = function (editor, event) {
113 113
114 114 // console.log('CM', this.mode, event.which, event.type)
115 115
116 116 if (this.mode === 'command') {
117 117 return true;
118 118 } else if (this.mode === 'edit') {
119 119 return this.handle_codemirror_keyevent(editor, event);
120 120 }
121 121 };
122 122
123 123 /**
124 124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
125 125 * handlers and is used to provide custom key handling.
126 126 *
127 127 * Subclass should override this method to have custom handeling
128 128 *
129 129 * @method handle_codemirror_keyevent
130 130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
131 131 * @param {event} event -
132 132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
133 133 */
134 134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
135 135 var that = this;
136 136
137 137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
138 138 // Always ignore shift-enter in CodeMirror as we handle it.
139 139 return true;
140 140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
141 141 // If we are not at the top, let CM handle the up arrow and
142 142 // prevent the global keydown handler from handling it.
143 143 if (!that.at_top()) {
144 144 event.stop();
145 145 return false;
146 146 } else {
147 147 return true;
148 148 };
149 149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
150 150 // If we are not at the bottom, let CM handle the down arrow and
151 151 // prevent the global keydown handler from handling it.
152 152 if (!that.at_bottom()) {
153 153 event.stop();
154 154 return false;
155 155 } else {
156 156 return true;
157 157 };
158 158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 160 // vim keyMap is active and in insert mode. In this case we leave vim
161 161 // insert mode, but remain in notebook edit mode.
162 162 // Let' CM handle this event and prevent global handling.
163 163 event.stop();
164 164 return false;
165 165 } else {
166 166 // vim keyMap is not active. Leave notebook edit mode.
167 167 // Don't let CM handle the event, defer to global handling.
168 168 return true;
169 169 }
170 170 }
171 171 return false;
172 172 };
173 173
174 174 // Cell level actions
175 175
176 176 TextCell.prototype.select = function () {
177 177 var cont = IPython.Cell.prototype.select.apply(this);
178 178 if (cont) {
179 179 if (this.mode === 'edit') {
180 180 this.code_mirror.refresh();
181 181 }
182 182 };
183 183 return cont;
184 184 };
185 185
186 186 TextCell.prototype.unrender = function () {
187 187 if (this.read_only) return;
188 188 var cont = IPython.Cell.prototype.unrender.apply(this);
189 189 if (cont) {
190 190 var text_cell = this.element;
191 191 var output = text_cell.find("div.text_cell_render");
192 192 output.hide();
193 193 text_cell.find('div.text_cell_input').show();
194 194 if (this.get_text() === this.placeholder) {
195 195 this.set_text('');
196 196 this.refresh();
197 197 }
198 198
199 199 };
200 200 return cont;
201 201 };
202 202
203 203 TextCell.prototype.execute = function () {
204 204 this.render();
205 205 };
206 206
207 207 TextCell.prototype.edit_mode = function () {
208 208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
209 209 if (cont) {
210 210 this.unrender();
211 211 this.focus_editor();
212 212 };
213 213 return cont;
214 214 }
215 215
216 216 /**
217 217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
218 218 * @method get_text
219 219 * @retrun {string} CodeMirror current text value
220 220 */
221 221 TextCell.prototype.get_text = function() {
222 222 return this.code_mirror.getValue();
223 223 };
224 224
225 225 /**
226 226 * @param {string} text - Codemiror text value
227 227 * @see TextCell#get_text
228 228 * @method set_text
229 229 * */
230 230 TextCell.prototype.set_text = function(text) {
231 231 this.code_mirror.setValue(text);
232 232 this.code_mirror.refresh();
233 233 };
234 234
235 235 /**
236 236 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
237 237 * @method get_rendered
238 238 * @return {html} html of rendered element
239 239 * */
240 240 TextCell.prototype.get_rendered = function() {
241 241 return this.element.find('div.text_cell_render').html();
242 242 };
243 243
244 244 /**
245 245 * @method set_rendered
246 246 */
247 247 TextCell.prototype.set_rendered = function(text) {
248 this.element.find('div.text_cell_render').html(text);
248 this.element.find('div.text_cell_render').text(text);
249 249 };
250 250
251 251 /**
252 252 * @method at_top
253 253 * @return {Boolean}
254 254 */
255 255 TextCell.prototype.at_top = function () {
256 256 if (this.rendered) {
257 257 return true;
258 258 } else {
259 259 var cursor = this.code_mirror.getCursor();
260 260 if (cursor.line === 0 && cursor.ch === 0) {
261 261 return true;
262 262 } else {
263 263 return false;
264 264 };
265 265 };
266 266 };
267 267
268 268 /**
269 269 * @method at_bottom
270 270 * @return {Boolean}
271 271 * */
272 272 TextCell.prototype.at_bottom = function () {
273 273 if (this.rendered) {
274 274 return true;
275 275 } else {
276 276 var cursor = this.code_mirror.getCursor();
277 277 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
278 278 return true;
279 279 } else {
280 280 return false;
281 281 };
282 282 };
283 283 };
284 284
285 285 /**
286 286 * Create Text cell from JSON
287 287 * @param {json} data - JSON serialized text-cell
288 288 * @method fromJSON
289 289 */
290 290 TextCell.prototype.fromJSON = function (data) {
291 291 IPython.Cell.prototype.fromJSON.apply(this, arguments);
292 292 if (data.cell_type === this.cell_type) {
293 293 if (data.source !== undefined) {
294 294 this.set_text(data.source);
295 295 // make this value the starting point, so that we can only undo
296 296 // to this state, instead of a blank cell
297 297 this.code_mirror.clearHistory();
298 298 this.set_rendered(data.rendered || '');
299 299 this.rendered = false;
300 300 this.render();
301 301 }
302 302 }
303 303 };
304 304
305 305 /** Generate JSON from cell
306 306 * @return {object} cell data serialised to json
307 307 */
308 308 TextCell.prototype.toJSON = function () {
309 309 var data = IPython.Cell.prototype.toJSON.apply(this);
310 310 data.source = this.get_text();
311 311 if (data.source == this.placeholder) {
312 312 data.source = "";
313 313 }
314 314 return data;
315 315 };
316 316
317 317
318 318 /**
319 319 * @class MarkdownCell
320 320 * @constructor MarkdownCell
321 321 * @extends IPython.HTMLCell
322 322 */
323 323 var MarkdownCell = function (options) {
324 324 options = this.mergeopt(MarkdownCell, options);
325 325
326 326 this.cell_type = 'markdown';
327 327 TextCell.apply(this, [options]);
328 328 };
329 329
330 330 MarkdownCell.options_default = {
331 331 cm_config: {
332 332 mode: 'gfm'
333 333 },
334 334 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
335 335 }
336 336
337 337 MarkdownCell.prototype = new TextCell();
338 338
339 339 /**
340 340 * @method render
341 341 */
342 342 MarkdownCell.prototype.render = function () {
343 343 var cont = IPython.TextCell.prototype.render.apply(this);
344 344 if (cont) {
345 345 var text = this.get_text();
346 346 var math = null;
347 347 if (text === "") { text = this.placeholder; }
348 348 var text_and_math = IPython.mathjaxutils.remove_math(text);
349 349 text = text_and_math[0];
350 350 math = text_and_math[1];
351 351 var html = marked.parser(marked.lexer(text));
352 352 html = $(IPython.mathjaxutils.replace_math(html, math));
353 // links in markdown cells should open in new tabs
353 // Links in markdown cells should open in new tabs.
354 354 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
355 355 try {
356 this.set_rendered(html);
356 // TODO: This HTML needs to be treated as potentially dangerous
357 // user input.
358 rendered.html(html);
357 359 } catch (e) {
358 360 console.log("Error running Javascript in Markdown:");
359 361 console.log(e);
360 this.set_rendered($("<div/>").addClass("js-error").html(
361 "Error rendering Markdown!<br/>" + e.toString())
362 rendered.empty();
363 rendered.append(
364 $("<div/>")
365 .append($("<div/>").text('Error rendering Markdown!').addClass("js-error"))
366 .append($("<div/>").text(e.toString()).addClass("js-error"))
362 367 );
363 368 }
364 369 this.element.find('div.text_cell_input').hide();
365 370 this.element.find("div.text_cell_render").show();
366 371 this.typeset()
367 372 };
368 373 return cont;
369 374 };
370 375
371 376
372 377 // RawCell
373 378
374 379 /**
375 380 * @class RawCell
376 381 * @constructor RawCell
377 382 * @extends IPython.TextCell
378 383 */
379 384 var RawCell = function (options) {
380 385
381 386 options = this.mergeopt(RawCell,options)
382 387 TextCell.apply(this, [options]);
383 388 this.cell_type = 'raw';
384 389 // RawCell should always hide its rendered div
385 390 this.element.find('div.text_cell_render').hide();
386 391 };
387 392
388 393 RawCell.options_default = {
389 394 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
390 395 "It will not be rendered in the notebook.\n" +
391 396 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
392 397 };
393 398
394 399 RawCell.prototype = new TextCell();
395 400
396 401 /** @method bind_events **/
397 402 RawCell.prototype.bind_events = function () {
398 403 TextCell.prototype.bind_events.apply(this);
399 404 var that = this
400 405 this.element.focusout(function() {
401 406 that.auto_highlight();
402 407 });
403 408 };
404 409
405 410 /**
406 411 * Trigger autodetection of highlight scheme for current cell
407 412 * @method auto_highlight
408 413 */
409 414 RawCell.prototype.auto_highlight = function () {
410 415 this._auto_highlight(IPython.config.raw_cell_highlight);
411 416 };
412 417
413 418 /** @method render **/
414 419 RawCell.prototype.render = function () {
415 420 // Make sure that this cell type can never be rendered
416 421 if (this.rendered) {
417 422 this.unrender();
418 423 }
419 424 var text = this.get_text();
420 425 if (text === "") { text = this.placeholder; }
421 426 this.set_text(text);
422 427 };
423 428
424 429
425 430 /**
426 431 * @class HeadingCell
427 432 * @extends IPython.TextCell
428 433 */
429 434
430 435 /**
431 436 * @constructor HeadingCell
432 437 * @extends IPython.TextCell
433 438 */
434 439 var HeadingCell = function (options) {
435 440 options = this.mergeopt(HeadingCell, options);
436 441
437 442 this.level = 1;
438 443 this.cell_type = 'heading';
439 444 TextCell.apply(this, [options]);
440 445
441 446 /**
442 447 * heading level of the cell, use getter and setter to access
443 448 * @property level
444 449 */
445 450 };
446 451
447 452 HeadingCell.options_default = {
448 453 placeholder: "Type Heading Here"
449 454 };
450 455
451 456 HeadingCell.prototype = new TextCell();
452 457
453 458 /** @method fromJSON */
454 459 HeadingCell.prototype.fromJSON = function (data) {
455 460 if (data.level != undefined){
456 461 this.level = data.level;
457 462 }
458 463 TextCell.prototype.fromJSON.apply(this, arguments);
459 464 };
460 465
461 466
462 467 /** @method toJSON */
463 468 HeadingCell.prototype.toJSON = function () {
464 469 var data = TextCell.prototype.toJSON.apply(this);
465 470 data.level = this.get_level();
466 471 return data;
467 472 };
468 473
469 474 /**
470 475 * can the cell be split into two cells
471 476 * @method is_splittable
472 477 **/
473 478 HeadingCell.prototype.is_splittable = function () {
474 479 return false;
475 480 };
476 481
477 482
478 483 /**
479 484 * can the cell be merged with other cells
480 485 * @method is_mergeable
481 486 **/
482 487 HeadingCell.prototype.is_mergeable = function () {
483 488 return false;
484 489 };
485 490
486 491 /**
487 492 * Change heading level of cell, and re-render
488 493 * @method set_level
489 494 */
490 495 HeadingCell.prototype.set_level = function (level) {
491 496 this.level = level;
492 497 if (this.rendered) {
493 498 this.rendered = false;
494 499 this.render();
495 500 };
496 501 };
497 502
498 503 /** The depth of header cell, based on html (h1 to h6)
499 504 * @method get_level
500 505 * @return {integer} level - for 1 to 6
501 506 */
502 507 HeadingCell.prototype.get_level = function () {
503 508 return this.level;
504 509 };
505 510
506 511
507 HeadingCell.prototype.set_rendered = function (html) {
508 this.element.find("div.text_cell_render").html(html);
509 };
510
511
512 512 HeadingCell.prototype.get_rendered = function () {
513 513 var r = this.element.find("div.text_cell_render");
514 514 return r.children().first().html();
515 515 };
516 516
517 517
518 518 HeadingCell.prototype.render = function () {
519 519 var cont = IPython.TextCell.prototype.render.apply(this);
520 520 if (cont) {
521 521 var text = this.get_text();
522 522 var math = null;
523 523 // Markdown headings must be a single line
524 524 text = text.replace(/\n/g, ' ');
525 525 if (text === "") { text = this.placeholder; }
526 526 text = Array(this.level + 1).join("#") + " " + text;
527 527 var text_and_math = IPython.mathjaxutils.remove_math(text);
528 528 text = text_and_math[0];
529 529 math = text_and_math[1];
530 530 var html = marked.parser(marked.lexer(text));
531 531 var h = $(IPython.mathjaxutils.replace_math(html, math));
532 532 // add id and linkback anchor
533 533 var hash = h.text().replace(/ /g, '-');
534 534 h.attr('id', hash);
535 535 h.append(
536 536 $('<a/>')
537 537 .addClass('anchor-link')
538 538 .attr('href', '#' + hash)
539 539 .text('ΒΆ')
540 540 );
541
542 this.set_rendered(h);
541 // TODO: This HTML needs to be treated as potentially dangerous
542 // user input.
543 var rendered = this.element.find("div.text_cell_render");
544 rendered.html(h);
543 545 this.typeset();
544 546 this.element.find('div.text_cell_input').hide();
545 this.element.find("div.text_cell_render").show();
547 rendered.show();
546 548
547 549 };
548 550 return cont;
549 551 };
550 552
551 553 IPython.TextCell = TextCell;
552 554 IPython.MarkdownCell = MarkdownCell;
553 555 IPython.RawCell = RawCell;
554 556 IPython.HeadingCell = HeadingCell;
555 557
556 558
557 559 return IPython;
558 560
559 561 }(IPython));
560 562
@@ -1,387 +1,388 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7 //============================================================================
8 8 // Tooltip
9 9 //============================================================================
10 10 //
11 11 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
12 12 //
13 13 // you can configure the differents action of pressing tab several times in a row by
14 14 // setting/appending different fonction in the array
15 15 // IPython.tooltip.tabs_functions
16 16 //
17 17 // eg :
18 18 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
19 19 //
20 20 var IPython = (function (IPython) {
21 21 "use strict";
22 22
23 23 var utils = IPython.utils;
24 24
25 25 // tooltip constructor
26 26 var Tooltip = function () {
27 27 var that = this;
28 28 this.time_before_tooltip = 1200;
29 29
30 30 // handle to html
31 31 this.tooltip = $('#tooltip');
32 32 this._hidden = true;
33 33
34 34 // variable for consecutive call
35 35 this._old_cell = null;
36 36 this._old_request = null;
37 37 this._consecutive_counter = 0;
38 38
39 39 // 'sticky ?'
40 40 this._sticky = false;
41 41
42 42 // display tooltip if the docstring is empty?
43 43 this._hide_if_no_docstring = false;
44 44
45 45 // contain the button in the upper right corner
46 46 this.buttons = $('<div/>').addClass('tooltipbuttons');
47 47
48 48 // will contain the docstring
49 49 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
50 50
51 51 // build the buttons menu on the upper right
52 52 // expand the tooltip to see more
53 53 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
54 54 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press tab 2 times)').click(function () {
55 55 that.expand()
56 56 }).append(
57 57 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
58 58
59 59 // open in pager
60 60 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press tab 4 times)');
61 61 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
62 62 morelink.append(morespan);
63 63 morelink.click(function () {
64 64 that.showInPager(that._old_cell);
65 65 });
66 66
67 67 // close the tooltip
68 68 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
69 69 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
70 70 closelink.append(closespan);
71 71 closelink.click(function () {
72 72 that.remove_and_cancel_tooltip(true);
73 73 });
74 74
75 75 this._clocklink = $('<a/>').attr('href', "#");
76 76 this._clocklink.attr('role', "button");
77 77 this._clocklink.addClass('ui-button');
78 78 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
79 79 var clockspan = $('<span/>').text('Close');
80 80 clockspan.addClass('ui-icon');
81 81 clockspan.addClass('ui-icon-clock');
82 82 this._clocklink.append(clockspan);
83 83 this._clocklink.click(function () {
84 84 that.cancel_stick();
85 85 });
86 86
87 87
88 88
89 89
90 90 //construct the tooltip
91 91 // add in the reverse order you want them to appear
92 92 this.buttons.append(closelink);
93 93 this.buttons.append(expandlink);
94 94 this.buttons.append(morelink);
95 95 this.buttons.append(this._clocklink);
96 96 this._clocklink.hide();
97 97
98 98
99 99 // we need a phony element to make the small arrow
100 100 // of the tooltip in css
101 101 // we will move the arrow later
102 102 this.arrow = $('<div/>').addClass('pretooltiparrow');
103 103 this.tooltip.append(this.buttons);
104 104 this.tooltip.append(this.arrow);
105 105 this.tooltip.append(this.text);
106 106
107 107 // function that will be called if you press tab 1, 2, 3... times in a row
108 108 this.tabs_functions = [function (cell, text) {
109 109 that._request_tooltip(cell, text);
110 110 }, function () {
111 111 that.expand();
112 112 }, function () {
113 113 that.stick();
114 114 }, function (cell) {
115 115 that.cancel_stick();
116 116 that.showInPager(cell);
117 117 }];
118 118 // call after all the tabs function above have bee call to clean their effects
119 119 // if necessary
120 120 this.reset_tabs_function = function (cell, text) {
121 121 this._old_cell = (cell) ? cell : null;
122 122 this._old_request = (text) ? text : null;
123 123 this._consecutive_counter = 0;
124 124 }
125 125 };
126 126
127 127 Tooltip.prototype.showInPager = function (cell) {
128 128 // reexecute last call in pager by appending ? to show back in pager
129 129 var that = this;
130 130 var empty = function () {};
131 131 cell.kernel.execute(
132 132 that.name + '?', {
133 133 'execute_reply': empty,
134 134 'output': empty,
135 135 'clear_output': empty,
136 136 'cell': cell
137 137 }, {
138 138 'silent': false,
139 139 'store_history': true
140 140 });
141 141 this.remove_and_cancel_tooltip();
142 142 }
143 143
144 144 // grow the tooltip verticaly
145 145 Tooltip.prototype.expand = function () {
146 146 this.text.removeClass('smalltooltip');
147 147 this.text.addClass('bigtooltip');
148 148 $('#expanbutton').hide('slow');
149 149 }
150 150
151 151 // deal with all the logic of hiding the tooltip
152 152 // and reset it's status
153 153 Tooltip.prototype._hide = function () {
154 154 this.tooltip.fadeOut('fast');
155 155 $('#expanbutton').show('slow');
156 156 this.text.removeClass('bigtooltip');
157 157 this.text.addClass('smalltooltip');
158 158 // keep scroll top to be sure to always see the first line
159 159 this.text.scrollTop(0);
160 160 this._hidden = true;
161 161 this.code_mirror = null;
162 162 }
163 163
164 164 // return true on successfully removing a visible tooltip; otherwise return
165 165 // false.
166 166 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
167 167 // note that we don't handle closing directly inside the calltip
168 168 // as in the completer, because it is not focusable, so won't
169 169 // get the event.
170 170 this.cancel_pending();
171 171 if (!this._hidden) {
172 172 if (force || !this._sticky) {
173 173 this.cancel_stick();
174 174 this._hide();
175 175 }
176 176 this.reset_tabs_function();
177 177 return true;
178 178 } else {
179 179 return false;
180 180 }
181 181 }
182 182
183 183 // cancel autocall done after '(' for example.
184 184 Tooltip.prototype.cancel_pending = function () {
185 185 if (this._tooltip_timeout != null) {
186 186 clearTimeout(this._tooltip_timeout);
187 187 this._tooltip_timeout = null;
188 188 }
189 189 }
190 190
191 191 // will trigger tooltip after timeout
192 192 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
193 193 var that = this;
194 194 this._tooltip_timeout = setTimeout(function () {
195 195 that.request(cell, hide_if_no_docstring)
196 196 }, that.time_before_tooltip);
197 197 }
198 198
199 199 // easy access for julia monkey patching.
200 200 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
201 201
202 202 Tooltip.prototype.extract_oir_token = function(line){
203 203 // use internally just to make the request to the kernel
204 204 // Feel free to shorten this logic if you are better
205 205 // than me in regEx
206 206 // basicaly you shoul be able to get xxx.xxx.xxx from
207 207 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
208 208 // remove everything between matchin bracket (need to iterate)
209 209 var matchBracket = /\([^\(\)]+\)/g;
210 210 var endBracket = /\([^\(]*$/g;
211 211 var oldline = line;
212 212
213 213 line = line.replace(matchBracket, "");
214 214 while (oldline != line) {
215 215 oldline = line;
216 216 line = line.replace(matchBracket, "");
217 217 }
218 218 // remove everything after last open bracket
219 219 line = line.replace(endBracket, "");
220 220 // reset the regex object
221 221 Tooltip.last_token_re.lastIndex = 0;
222 222 return Tooltip.last_token_re.exec(line)
223 223 };
224 224
225 225 Tooltip.prototype._request_tooltip = function (cell, line) {
226 226 var callbacks = $.proxy(this._show, this);
227 227 var oir_token = this.extract_oir_token(line);
228 228 var msg_id = cell.kernel.object_info(oir_token, callbacks);
229 229 };
230 230
231 231 // make an imediate completion request
232 232 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
233 233 // request(codecell)
234 234 // Deal with extracting the text from the cell and counting
235 235 // call in a row
236 236 this.cancel_pending();
237 237 var editor = cell.code_mirror;
238 238 var cursor = editor.getCursor();
239 239 var text = editor.getRange({
240 240 line: cursor.line,
241 241 ch: 0
242 242 }, cursor).trim();
243 243
244 244 this._hide_if_no_docstring = hide_if_no_docstring;
245 245
246 246 if(editor.somethingSelected()){
247 247 text = editor.getSelection();
248 248 }
249 249
250 250 // need a permanent handel to code_mirror for future auto recall
251 251 this.code_mirror = editor;
252 252
253 253 // now we treat the different number of keypress
254 254 // first if same cell, same text, increment counter by 1
255 255 if (this._old_cell == cell && this._old_request == text && this._hidden == false) {
256 256 this._consecutive_counter++;
257 257 } else {
258 258 // else reset
259 259 this.cancel_stick();
260 260 this.reset_tabs_function (cell, text);
261 261 }
262 262
263 263 // don't do anything if line beggin with '(' or is empty
264 264 if (text === "" || text === "(") {
265 265 return;
266 266 }
267 267
268 268 this.tabs_functions[this._consecutive_counter](cell, text);
269 269
270 270 // then if we are at the end of list function, reset
271 271 if (this._consecutive_counter == this.tabs_functions.length) {
272 272 this.reset_tabs_function (cell, text);
273 273 }
274 274
275 275 return;
276 276 }
277 277
278 278 // cancel the option of having the tooltip to stick
279 279 Tooltip.prototype.cancel_stick = function () {
280 280 clearTimeout(this._stick_timeout);
281 281 this._stick_timeout = null;
282 282 this._clocklink.hide('slow');
283 283 this._sticky = false;
284 284 }
285 285
286 286 // put the tooltip in a sicky state for 10 seconds
287 287 // it won't be removed by remove_and_cancell() unless you called with
288 288 // the first parameter set to true.
289 289 // remove_and_cancell_tooltip(true)
290 290 Tooltip.prototype.stick = function (time) {
291 291 time = (time != undefined) ? time : 10;
292 292 var that = this;
293 293 this._sticky = true;
294 294 this._clocklink.show('slow');
295 295 this._stick_timeout = setTimeout(function () {
296 296 that._sticky = false;
297 297 that._clocklink.hide('slow');
298 298 }, time * 1000);
299 299 }
300 300
301 301 // should be called with the kernel reply to actually show the tooltip
302 302 Tooltip.prototype._show = function (reply) {
303 303 // move the bubble if it is not hidden
304 304 // otherwise fade it
305 305 var content = reply.content;
306 306 if (!content.found) {
307 307 // object not found, nothing to show
308 308 return;
309 309 }
310 310 this.name = content.name;
311 311
312 312 // do some math to have the tooltip arrow on more or less on left or right
313 313 // width of the editor
314 314 var w = $(this.code_mirror.getScrollerElement()).width();
315 315 // ofset of the editor
316 316 var o = $(this.code_mirror.getScrollerElement()).offset();
317 317
318 318 // whatever anchor/head order but arrow at mid x selection
319 319 var anchor = this.code_mirror.cursorCoords(false);
320 320 var head = this.code_mirror.cursorCoords(true);
321 321 var xinit = (head.left+anchor.left)/2;
322 322 var xinter = o.left + (xinit - o.left) / w * (w - 450);
323 323 var posarrowleft = xinit - xinter;
324 324
325 325 if (this._hidden == false) {
326 326 this.tooltip.animate({
327 327 'left': xinter - 30 + 'px',
328 328 'top': (head.bottom + 10) + 'px'
329 329 });
330 330 } else {
331 331 this.tooltip.css({
332 332 'left': xinter - 30 + 'px'
333 333 });
334 334 this.tooltip.css({
335 335 'top': (head.bottom + 10) + 'px'
336 336 });
337 337 }
338 338 this.arrow.animate({
339 339 'left': posarrowleft + 'px'
340 340 });
341 341
342 342 // build docstring
343 343 var defstring = content.call_def;
344 344 if (!defstring) {
345 345 defstring = content.init_definition;
346 346 }
347 347 if (!defstring) {
348 348 defstring = content.definition;
349 349 }
350 350
351 351 var docstring = content.call_docstring;
352 352 if (!docstring) {
353 353 docstring = content.init_docstring;
354 354 }
355 355 if (!docstring) {
356 356 docstring = content.docstring;
357 357 }
358 358
359 359 if (!docstring) {
360 360 // For reals this time, no docstring
361 361 if (this._hide_if_no_docstring) {
362 362 return;
363 363 } else {
364 364 docstring = "<empty docstring>";
365 365 }
366 366 }
367 367
368 368 this.tooltip.fadeIn('fast');
369 369 this._hidden = false;
370 370 this.text.children().remove();
371 371
372 // Any HTML within the docstring is escaped by the fixConsole() method.
372 373 var pre = $('<pre/>').html(utils.fixConsole(docstring));
373 374 if (defstring) {
374 375 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
375 376 this.text.append(defstring_html);
376 377 }
377 378 this.text.append(pre);
378 379 // keep scroll top to be sure to always see the first line
379 380 this.text.scrollTop(0);
380 381 }
381 382
382 383
383 384 IPython.Tooltip = Tooltip;
384 385
385 386 return IPython;
386 387
387 388 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now