##// END OF EJS Templates
Merge codemirror config with user config....
Matthias Bussonnier -
Show More
@@ -1,707 +1,711 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 /**
5 5 *
6 6 *
7 7 * @module cell
8 8 * @namespace cell
9 9 * @class Cell
10 10 */
11 11
12 12
13 13 define([
14 14 'base/js/namespace',
15 15 'jquery',
16 16 'base/js/utils',
17 17 'codemirror/lib/codemirror',
18 18 'codemirror/addon/edit/matchbrackets',
19 19 'codemirror/addon/edit/closebrackets',
20 20 'codemirror/addon/comment/comment'
21 21 ], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
22 22 // TODO: remove IPython dependency here
23 23 "use strict";
24 24
25 25 var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
26 26
27 27 CodeMirror.scrollbarModel.native.prototype.overlayHack = function () {
28 28 overlayHack.apply(this, arguments);
29 29 // Reverse `min-height: 18px` scrollbar hack on OS X
30 30 // which causes a dead area, making it impossible to click on the last line
31 31 // when there is horizontal scrolling to do and the "show scrollbar only when scrolling" behavior
32 32 // is enabled.
33 33 // This, in turn, has the undesirable behavior of never showing the horizontal scrollbar,
34 34 // even when it should, which is less problematic, at least.
35 35 if (/Mac/.test(navigator.platform)) {
36 36 this.horiz.style.minHeight = "";
37 37 }
38 38 };
39 39
40 40 var Cell = function (options) {
41 41 /* Constructor
42 42 *
43 43 * The Base `Cell` class from which to inherit.
44 44 * @constructor
45 45 * @param:
46 46 * options: dictionary
47 47 * Dictionary of keyword arguments.
48 48 * events: $(Events) instance
49 49 * config: dictionary
50 50 * keyboard_manager: KeyboardManager instance
51 51 */
52 52 options = options || {};
53 53 this.keyboard_manager = options.keyboard_manager;
54 54 this.events = options.events;
55 55 var config = utils.mergeopt(Cell, options.config);
56 56 // superclass default overwrite our default
57 57
58 58 this.placeholder = config.placeholder || '';
59 this.read_only = config.cm_config.readOnly;
60 59 this.selected = false;
61 60 this.rendered = false;
62 61 this.mode = 'command';
63 62
64 63 // Metadata property
65 64 var that = this;
66 65 this._metadata = {};
67 66 Object.defineProperty(this, 'metadata', {
68 67 get: function() { return that._metadata; },
69 68 set: function(value) {
70 69 that._metadata = value;
71 70 if (that.celltoolbar) {
72 71 that.celltoolbar.rebuild();
73 72 }
74 73 }
75 74 });
76 75
77 76 // load this from metadata later ?
78 77 this.user_highlight = 'auto';
79 this.cm_config = config.cm_config;
78
79 var class_conf_value = {};
80 if(this.class_config){
81 class_conf_value = this.class_config.get_sync('cm_config');
82 }
83 this.cm_config = utils.mergeopt({}, config.cm_config, class_conf_value);
80 84 this.cell_id = utils.uuid();
81 85 this._options = config;
82 86
83 87 // For JS VM engines optimization, attributes should be all set (even
84 88 // to null) in the constructor, and if possible, if different subclass
85 89 // have new attributes with same name, they should be created in the
86 90 // same order. Easiest is to create and set to null in parent class.
87 91
88 92 this.element = null;
89 93 this.cell_type = this.cell_type || null;
90 94 this.code_mirror = null;
91 95
92 96 this.create_element();
93 97 if (this.element !== null) {
94 98 this.element.data("cell", this);
95 99 this.bind_events();
96 100 this.init_classes();
97 101 }
98 102 };
99 103
100 104 Cell.options_default = {
101 105 cm_config : {
102 106 indentUnit : 4,
103 107 readOnly: false,
104 108 theme: "default",
105 109 extraKeys: {
106 110 "Cmd-Right":"goLineRight",
107 111 "End":"goLineRight",
108 112 "Cmd-Left":"goLineLeft"
109 113 }
110 114 }
111 115 };
112 116
113 117 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
114 118 // by disabling drag/drop altogether on Safari
115 119 // https://github.com/codemirror/CodeMirror/issues/332
116 120 if (utils.browser[0] == "Safari") {
117 121 Cell.options_default.cm_config.dragDrop = false;
118 122 }
119 123
120 124 /**
121 125 * Empty. Subclasses must implement create_element.
122 126 * This should contain all the code to create the DOM element in notebook
123 127 * and will be called by Base Class constructor.
124 128 * @method create_element
125 129 */
126 130 Cell.prototype.create_element = function () {
127 131 };
128 132
129 133 Cell.prototype.init_classes = function () {
130 134 /**
131 135 * Call after this.element exists to initialize the css classes
132 136 * related to selected, rendered and mode.
133 137 */
134 138 if (this.selected) {
135 139 this.element.addClass('selected');
136 140 } else {
137 141 this.element.addClass('unselected');
138 142 }
139 143 if (this.rendered) {
140 144 this.element.addClass('rendered');
141 145 } else {
142 146 this.element.addClass('unrendered');
143 147 }
144 148 };
145 149
146 150 /**
147 151 * Subclasses can implement override bind_events.
148 152 * Be carefull to call the parent method when overwriting as it fires event.
149 153 * this will be triggerd after create_element in constructor.
150 154 * @method bind_events
151 155 */
152 156 Cell.prototype.bind_events = function () {
153 157 var that = this;
154 158 // We trigger events so that Cell doesn't have to depend on Notebook.
155 159 that.element.click(function (event) {
156 160 if (!that.selected) {
157 161 that.events.trigger('select.Cell', {'cell':that});
158 162 }
159 163 });
160 164 that.element.focusin(function (event) {
161 165 if (!that.selected) {
162 166 that.events.trigger('select.Cell', {'cell':that});
163 167 }
164 168 });
165 169 if (this.code_mirror) {
166 170 this.code_mirror.on("change", function(cm, change) {
167 171 that.events.trigger("set_dirty.Notebook", {value: true});
168 172 });
169 173 }
170 174 if (this.code_mirror) {
171 175 this.code_mirror.on('focus', function(cm, change) {
172 176 that.events.trigger('edit_mode.Cell', {cell: that});
173 177 });
174 178 }
175 179 if (this.code_mirror) {
176 180 this.code_mirror.on('blur', function(cm, change) {
177 181 that.events.trigger('command_mode.Cell', {cell: that});
178 182 });
179 183 }
180 184
181 185 this.element.dblclick(function () {
182 186 if (that.selected === false) {
183 187 this.events.trigger('select.Cell', {'cell':that});
184 188 }
185 189 var cont = that.unrender();
186 190 if (cont) {
187 191 that.focus_editor();
188 192 }
189 193 });
190 194 };
191 195
192 196 /**
193 197 * This method gets called in CodeMirror's onKeyDown/onKeyPress
194 198 * handlers and is used to provide custom key handling.
195 199 *
196 200 * To have custom handling, subclasses should override this method, but still call it
197 201 * in order to process the Edit mode keyboard shortcuts.
198 202 *
199 203 * @method handle_codemirror_keyevent
200 204 * @param {CodeMirror} editor - The codemirror instance bound to the cell
201 205 * @param {event} event - key press event which either should or should not be handled by CodeMirror
202 206 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
203 207 */
204 208 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
205 209 var shortcuts = this.keyboard_manager.edit_shortcuts;
206 210
207 211 var cur = editor.getCursor();
208 212 if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
209 213 event._ipkmIgnore = true;
210 214 }
211 215 var nLastLine = editor.lastLine();
212 216 if ((event.keyCode === 40) &&
213 217 ((cur.line !== nLastLine) ||
214 218 (cur.ch !== editor.getLineHandle(nLastLine).text.length))
215 219 ) {
216 220 event._ipkmIgnore = true;
217 221 }
218 222 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
219 223 // manager will handle it
220 224 if (shortcuts.handles(event)) {
221 225 return true;
222 226 }
223 227
224 228 return false;
225 229 };
226 230
227 231
228 232 /**
229 233 * Triger typsetting of math by mathjax on current cell element
230 234 * @method typeset
231 235 */
232 236 Cell.prototype.typeset = function () {
233 237 utils.typeset(this.element);
234 238 };
235 239
236 240 /**
237 241 * handle cell level logic when a cell is selected
238 242 * @method select
239 243 * @return is the action being taken
240 244 */
241 245 Cell.prototype.select = function () {
242 246 if (!this.selected) {
243 247 this.element.addClass('selected');
244 248 this.element.removeClass('unselected');
245 249 this.selected = true;
246 250 return true;
247 251 } else {
248 252 return false;
249 253 }
250 254 };
251 255
252 256 /**
253 257 * handle cell level logic when a cell is unselected
254 258 * @method unselect
255 259 * @return is the action being taken
256 260 */
257 261 Cell.prototype.unselect = function () {
258 262 if (this.selected) {
259 263 this.element.addClass('unselected');
260 264 this.element.removeClass('selected');
261 265 this.selected = false;
262 266 return true;
263 267 } else {
264 268 return false;
265 269 }
266 270 };
267 271
268 272 /**
269 273 * should be overritten by subclass
270 274 * @method execute
271 275 */
272 276 Cell.prototype.execute = function () {
273 277 return;
274 278 };
275 279
276 280 /**
277 281 * handle cell level logic when a cell is rendered
278 282 * @method render
279 283 * @return is the action being taken
280 284 */
281 285 Cell.prototype.render = function () {
282 286 if (!this.rendered) {
283 287 this.element.addClass('rendered');
284 288 this.element.removeClass('unrendered');
285 289 this.rendered = true;
286 290 return true;
287 291 } else {
288 292 return false;
289 293 }
290 294 };
291 295
292 296 /**
293 297 * handle cell level logic when a cell is unrendered
294 298 * @method unrender
295 299 * @return is the action being taken
296 300 */
297 301 Cell.prototype.unrender = function () {
298 302 if (this.rendered) {
299 303 this.element.addClass('unrendered');
300 304 this.element.removeClass('rendered');
301 305 this.rendered = false;
302 306 return true;
303 307 } else {
304 308 return false;
305 309 }
306 310 };
307 311
308 312 /**
309 313 * Delegates keyboard shortcut handling to either IPython keyboard
310 314 * manager when in command mode, or CodeMirror when in edit mode
311 315 *
312 316 * @method handle_keyevent
313 317 * @param {CodeMirror} editor - The codemirror instance bound to the cell
314 318 * @param {event} - key event to be handled
315 319 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
316 320 */
317 321 Cell.prototype.handle_keyevent = function (editor, event) {
318 322 if (this.mode === 'command') {
319 323 return true;
320 324 } else if (this.mode === 'edit') {
321 325 return this.handle_codemirror_keyevent(editor, event);
322 326 }
323 327 };
324 328
325 329 /**
326 330 * @method at_top
327 331 * @return {Boolean}
328 332 */
329 333 Cell.prototype.at_top = function () {
330 334 var cm = this.code_mirror;
331 335 var cursor = cm.getCursor();
332 336 if (cursor.line === 0 && cursor.ch === 0) {
333 337 return true;
334 338 }
335 339 return false;
336 340 };
337 341
338 342 /**
339 343 * @method at_bottom
340 344 * @return {Boolean}
341 345 * */
342 346 Cell.prototype.at_bottom = function () {
343 347 var cm = this.code_mirror;
344 348 var cursor = cm.getCursor();
345 349 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
346 350 return true;
347 351 }
348 352 return false;
349 353 };
350 354
351 355 /**
352 356 * enter the command mode for the cell
353 357 * @method command_mode
354 358 * @return is the action being taken
355 359 */
356 360 Cell.prototype.command_mode = function () {
357 361 if (this.mode !== 'command') {
358 362 this.mode = 'command';
359 363 return true;
360 364 } else {
361 365 return false;
362 366 }
363 367 };
364 368
365 369 /**
366 370 * enter the edit mode for the cell
367 371 * @method command_mode
368 372 * @return is the action being taken
369 373 */
370 374 Cell.prototype.edit_mode = function () {
371 375 if (this.mode !== 'edit') {
372 376 this.mode = 'edit';
373 377 return true;
374 378 } else {
375 379 return false;
376 380 }
377 381 };
378 382
379 383 Cell.prototype.ensure_focused = function() {
380 384 if(this.element !== document.activeElement && !this.code_mirror.hasFocus()){
381 385 this.focus_cell();
382 386 }
383 387 }
384 388
385 389 /**
386 390 * Focus the cell in the DOM sense
387 391 * @method focus_cell
388 392 */
389 393 Cell.prototype.focus_cell = function () {
390 394 this.element.focus();
391 395 };
392 396
393 397 /**
394 398 * Focus the editor area so a user can type
395 399 *
396 400 * NOTE: If codemirror is focused via a mouse click event, you don't want to
397 401 * call this because it will cause a page jump.
398 402 * @method focus_editor
399 403 */
400 404 Cell.prototype.focus_editor = function () {
401 405 this.refresh();
402 406 this.code_mirror.focus();
403 407 };
404 408
405 409 /**
406 410 * Refresh codemirror instance
407 411 * @method refresh
408 412 */
409 413 Cell.prototype.refresh = function () {
410 414 if (this.code_mirror) {
411 415 this.code_mirror.refresh();
412 416 }
413 417 };
414 418
415 419 /**
416 420 * should be overritten by subclass
417 421 * @method get_text
418 422 */
419 423 Cell.prototype.get_text = function () {
420 424 };
421 425
422 426 /**
423 427 * should be overritten by subclass
424 428 * @method set_text
425 429 * @param {string} text
426 430 */
427 431 Cell.prototype.set_text = function (text) {
428 432 };
429 433
430 434 /**
431 435 * should be overritten by subclass
432 436 * serialise cell to json.
433 437 * @method toJSON
434 438 **/
435 439 Cell.prototype.toJSON = function () {
436 440 var data = {};
437 441 // deepcopy the metadata so copied cells don't share the same object
438 442 data.metadata = JSON.parse(JSON.stringify(this.metadata));
439 443 data.cell_type = this.cell_type;
440 444 return data;
441 445 };
442 446
443 447 /**
444 448 * should be overritten by subclass
445 449 * @method fromJSON
446 450 **/
447 451 Cell.prototype.fromJSON = function (data) {
448 452 if (data.metadata !== undefined) {
449 453 this.metadata = data.metadata;
450 454 }
451 455 };
452 456
453 457
454 458 /**
455 459 * can the cell be split into two cells (false if not deletable)
456 460 * @method is_splittable
457 461 **/
458 462 Cell.prototype.is_splittable = function () {
459 463 return this.is_deletable();
460 464 };
461 465
462 466
463 467 /**
464 468 * can the cell be merged with other cells (false if not deletable)
465 469 * @method is_mergeable
466 470 **/
467 471 Cell.prototype.is_mergeable = function () {
468 472 return this.is_deletable();
469 473 };
470 474
471 475 /**
472 476 * is the cell deletable? only false (undeletable) if
473 477 * metadata.deletable is explicitly false -- everything else
474 478 * counts as true
475 479 *
476 480 * @method is_deletable
477 481 **/
478 482 Cell.prototype.is_deletable = function () {
479 483 if (this.metadata.deletable === false) {
480 484 return false;
481 485 }
482 486 return true;
483 487 };
484 488
485 489 /**
486 490 * @return {String} - the text before the cursor
487 491 * @method get_pre_cursor
488 492 **/
489 493 Cell.prototype.get_pre_cursor = function () {
490 494 var cursor = this.code_mirror.getCursor();
491 495 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
492 496 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
493 497 return text;
494 498 };
495 499
496 500
497 501 /**
498 502 * @return {String} - the text after the cursor
499 503 * @method get_post_cursor
500 504 **/
501 505 Cell.prototype.get_post_cursor = function () {
502 506 var cursor = this.code_mirror.getCursor();
503 507 var last_line_num = this.code_mirror.lineCount()-1;
504 508 var last_line_len = this.code_mirror.getLine(last_line_num).length;
505 509 var end = {line:last_line_num, ch:last_line_len};
506 510 var text = this.code_mirror.getRange(cursor, end);
507 511 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
508 512 return text;
509 513 };
510 514
511 515 /**
512 516 * Show/Hide CodeMirror LineNumber
513 517 * @method show_line_numbers
514 518 *
515 519 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
516 520 **/
517 521 Cell.prototype.show_line_numbers = function (value) {
518 522 this.code_mirror.setOption('lineNumbers', value);
519 523 this.code_mirror.refresh();
520 524 };
521 525
522 526 /**
523 527 * Toggle CodeMirror LineNumber
524 528 * @method toggle_line_numbers
525 529 **/
526 530 Cell.prototype.toggle_line_numbers = function () {
527 531 var val = this.code_mirror.getOption('lineNumbers');
528 532 this.show_line_numbers(!val);
529 533 };
530 534
531 535 /**
532 536 * Force codemirror highlight mode
533 537 * @method force_highlight
534 538 * @param {object} - CodeMirror mode
535 539 **/
536 540 Cell.prototype.force_highlight = function(mode) {
537 541 this.user_highlight = mode;
538 542 this.auto_highlight();
539 543 };
540 544
541 545 /**
542 546 * Trigger autodetection of highlight scheme for current cell
543 547 * @method auto_highlight
544 548 */
545 549 Cell.prototype.auto_highlight = function () {
546 550 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
547 551 };
548 552
549 553 /**
550 554 * Try to autodetect cell highlight mode, or use selected mode
551 555 * @methods _auto_highlight
552 556 * @private
553 557 * @param {String|object|undefined} - CodeMirror mode | 'auto'
554 558 **/
555 559 Cell.prototype._auto_highlight = function (modes) {
556 560 /**
557 561 *Here we handle manually selected modes
558 562 */
559 563 var that = this;
560 564 var mode;
561 565 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
562 566 {
563 567 mode = this.user_highlight;
564 568 CodeMirror.autoLoadMode(this.code_mirror, mode);
565 569 this.code_mirror.setOption('mode', mode);
566 570 return;
567 571 }
568 572 var current_mode = this.code_mirror.getOption('mode', mode);
569 573 var first_line = this.code_mirror.getLine(0);
570 574 // loop on every pairs
571 575 for(mode in modes) {
572 576 var regs = modes[mode].reg;
573 577 // only one key every time but regexp can't be keys...
574 578 for(var i=0; i<regs.length; i++) {
575 579 // here we handle non magic_modes.
576 580 // TODO :
577 581 // On 3.0 and below, these things were regex.
578 582 // But now should be string for json-able config.
579 583 // We should get rid of assuming they might be already
580 584 // in a later version of IPython.
581 585 var re = regs[i];
582 586 if(typeof(re) === 'string'){
583 587 re = new RegExp(re)
584 588 }
585 589 if(first_line.match(re) !== null) {
586 590 if(current_mode == mode){
587 591 return;
588 592 }
589 593 if (mode.search('magic_') !== 0) {
590 594 utils.requireCodeMirrorMode(mode, function (spec) {
591 595 that.code_mirror.setOption('mode', spec);
592 596 });
593 597 return;
594 598 }
595 599 var open = modes[mode].open || "%%";
596 600 var close = modes[mode].close || "%%end";
597 601 var magic_mode = mode;
598 602 mode = magic_mode.substr(6);
599 603 if(current_mode == magic_mode){
600 604 return;
601 605 }
602 606 utils.requireCodeMirrorMode(mode, function (spec) {
603 607 // create on the fly a mode that switch between
604 608 // plain/text and something else, otherwise `%%` is
605 609 // source of some highlight issues.
606 610 CodeMirror.defineMode(magic_mode, function(config) {
607 611 return CodeMirror.multiplexingMode(
608 612 CodeMirror.getMode(config, 'text/plain'),
609 613 // always set something on close
610 614 {open: open, close: close,
611 615 mode: CodeMirror.getMode(config, spec),
612 616 delimStyle: "delimit"
613 617 }
614 618 );
615 619 });
616 620 that.code_mirror.setOption('mode', magic_mode);
617 621 });
618 622 return;
619 623 }
620 624 }
621 625 }
622 626 // fallback on default
623 627 var default_mode;
624 628 try {
625 629 default_mode = this._options.cm_config.mode;
626 630 } catch(e) {
627 631 default_mode = 'text/plain';
628 632 }
629 633 if( current_mode === default_mode){
630 634 return;
631 635 }
632 636 this.code_mirror.setOption('mode', default_mode);
633 637 };
634 638
635 639 var UnrecognizedCell = function (options) {
636 640 /** Constructor for unrecognized cells */
637 641 Cell.apply(this, arguments);
638 642 this.cell_type = 'unrecognized';
639 643 this.celltoolbar = null;
640 644 this.data = {};
641 645
642 646 Object.seal(this);
643 647 };
644 648
645 649 UnrecognizedCell.prototype = Object.create(Cell.prototype);
646 650
647 651
648 652 // cannot merge or split unrecognized cells
649 653 UnrecognizedCell.prototype.is_mergeable = function () {
650 654 return false;
651 655 };
652 656
653 657 UnrecognizedCell.prototype.is_splittable = function () {
654 658 return false;
655 659 };
656 660
657 661 UnrecognizedCell.prototype.toJSON = function () {
658 662 /**
659 663 * deepcopy the metadata so copied cells don't share the same object
660 664 */
661 665 return JSON.parse(JSON.stringify(this.data));
662 666 };
663 667
664 668 UnrecognizedCell.prototype.fromJSON = function (data) {
665 669 this.data = data;
666 670 if (data.metadata !== undefined) {
667 671 this.metadata = data.metadata;
668 672 } else {
669 673 data.metadata = this.metadata;
670 674 }
671 675 this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
672 676 };
673 677
674 678 UnrecognizedCell.prototype.create_element = function () {
675 679 Cell.prototype.create_element.apply(this, arguments);
676 680 var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
677 681 cell.attr('tabindex','2');
678 682
679 683 var prompt = $('<div/>').addClass('prompt input_prompt');
680 684 cell.append(prompt);
681 685 var inner_cell = $('<div/>').addClass('inner_cell');
682 686 inner_cell.append(
683 687 $("<a>")
684 688 .attr("href", "#")
685 689 .text("Unrecognized cell type")
686 690 );
687 691 cell.append(inner_cell);
688 692 this.element = cell;
689 693 };
690 694
691 695 UnrecognizedCell.prototype.bind_events = function () {
692 696 Cell.prototype.bind_events.apply(this, arguments);
693 697 var cell = this;
694 698
695 699 this.element.find('.inner_cell').find("a").click(function () {
696 700 cell.events.trigger('unrecognized_cell.Cell', {cell: cell});
697 701 });
698 702 };
699 703
700 704 // Backwards compatibility.
701 705 IPython.Cell = Cell;
702 706
703 707 return {
704 708 Cell: Cell,
705 709 UnrecognizedCell: UnrecognizedCell
706 710 };
707 711 });
@@ -1,664 +1,664 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3 /**
4 4 *
5 5 *
6 6 * @module codecell
7 7 * @namespace codecell
8 8 * @class CodeCell
9 9 */
10 10
11 11
12 12 define([
13 13 'base/js/namespace',
14 14 'jquery',
15 15 'base/js/utils',
16 16 'base/js/keyboard',
17 17 'services/config',
18 18 'notebook/js/cell',
19 19 'notebook/js/outputarea',
20 20 'notebook/js/completer',
21 21 'notebook/js/celltoolbar',
22 22 'codemirror/lib/codemirror',
23 23 'codemirror/mode/python/python',
24 24 'notebook/js/codemirror-ipython'
25 25 ], function(IPython,
26 26 $,
27 27 utils,
28 28 keyboard,
29 29 configmod,
30 30 cell,
31 31 outputarea,
32 32 completer,
33 33 celltoolbar,
34 34 CodeMirror,
35 35 cmpython,
36 36 cmip
37 37 ) {
38 38 "use strict";
39 39
40 40 var Cell = cell.Cell;
41 41
42 42 /* local util for codemirror */
43 43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44 44
45 45 /**
46 46 *
47 47 * function to delete until previous non blanking space character
48 48 * or first multiple of 4 tabstop.
49 49 * @private
50 50 */
51 51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 55 var tabsize = cm.getOption('tabSize');
56 56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 58 var select = cm.getRange(from,cur);
59 59 if( select.match(/^\ +$/) !== null){
60 60 cm.replaceRange("",from,cur);
61 61 } else {
62 62 cm.deleteH(-1,"char");
63 63 }
64 64 };
65 65
66 66 var keycodes = keyboard.keycodes;
67 67
68 68 var CodeCell = function (kernel, options) {
69 69 /**
70 70 * Constructor
71 71 *
72 72 * A Cell conceived to write code.
73 73 *
74 74 * Parameters:
75 75 * kernel: Kernel instance
76 76 * The kernel doesn't have to be set at creation time, in that case
77 77 * it will be null and set_kernel has to be called later.
78 78 * options: dictionary
79 79 * Dictionary of keyword arguments.
80 80 * events: $(Events) instance
81 81 * config: dictionary
82 82 * keyboard_manager: KeyboardManager instance
83 83 * notebook: Notebook instance
84 84 * tooltip: Tooltip instance
85 85 */
86 86 this.kernel = kernel || null;
87 87 this.notebook = options.notebook;
88 88 this.collapsed = false;
89 89 this.events = options.events;
90 90 this.tooltip = options.tooltip;
91 91 this.config = options.config;
92 92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 93 CodeCell.config_defaults, 'CodeCell');
94 94
95 95 // create all attributed in constructor function
96 96 // even if null for V8 VM optimisation
97 97 this.input_prompt_number = null;
98 98 this.celltoolbar = null;
99 99 this.output_area = null;
100 100
101 101 this.last_msg_id = null;
102 102 this.completer = null;
103 103 this.widget_views = [];
104 104 this._widgets_live = true;
105 105
106 106 Cell.apply(this,[{
107 107 config: $.extend({}, CodeCell.options_default),
108 108 keyboard_manager: options.keyboard_manager,
109 109 events: this.events}]);
110 110
111 111 // Attributes we want to override in this subclass.
112 112 this.cell_type = "code";
113 113 var that = this;
114 114 this.element.focusout(
115 115 function() { that.auto_highlight(); }
116 116 );
117 117 };
118 118
119 119 CodeCell.options_default = {
120 120 cm_config : {
121 121 extraKeys: {
122 122 "Tab" : "indentMore",
123 123 "Shift-Tab" : "indentLess",
124 124 "Backspace" : "delSpaceToPrevTabStop",
125 125 "Cmd-/" : "toggleComment",
126 126 "Ctrl-/" : "toggleComment"
127 127 },
128 128 mode: 'ipython',
129 129 theme: 'ipython',
130 130 matchBrackets: true,
131 131 autoCloseBrackets: true
132 132 },
133 133 highlight_modes : {
134 134 'magic_javascript' :{'reg':['^%%javascript']},
135 135 'magic_perl' :{'reg':['^%%perl']},
136 136 'magic_ruby' :{'reg':['^%%ruby']},
137 137 'magic_python' :{'reg':['^%%python3?']},
138 138 'magic_shell' :{'reg':['^%%bash']},
139 139 'magic_r' :{'reg':['^%%R']},
140 140 'magic_text/x-cython' :{'reg':['^%%cython']},
141 141 },
142 142 };
143 143
144 144 CodeCell.config_defaults = CodeCell.options_default;
145 145
146 146 CodeCell.msg_cells = {};
147 147
148 148 CodeCell.prototype = Object.create(Cell.prototype);
149 149
150 150 /** @method create_element */
151 151 CodeCell.prototype.create_element = function () {
152 152 Cell.prototype.create_element.apply(this, arguments);
153 153 var that = this;
154 154
155 155 var cell = $('<div></div>').addClass('cell code_cell');
156 156 cell.attr('tabindex','2');
157 157
158 158 var input = $('<div></div>').addClass('input');
159 159 var prompt = $('<div/>').addClass('prompt input_prompt');
160 160 var inner_cell = $('<div/>').addClass('inner_cell');
161 161 this.celltoolbar = new celltoolbar.CellToolbar({
162 162 cell: this,
163 163 notebook: this.notebook});
164 164 inner_cell.append(this.celltoolbar.element);
165 165 var input_area = $('<div/>').addClass('input_area');
166 this.code_mirror = new CodeMirror(input_area.get(0), this.class_config.get_sync('cm_config'));
166 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
167 167 // In case of bugs that put the keyboard manager into an inconsistent state,
168 168 // ensure KM is enabled when CodeMirror is focused:
169 169 this.code_mirror.on('focus', function () {
170 170 if (that.keyboard_manager) {
171 171 that.keyboard_manager.enable();
172 172 }
173 173 });
174 174 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
175 175 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
176 176 inner_cell.append(input_area);
177 177 input.append(prompt).append(inner_cell);
178 178
179 179 var widget_area = $('<div/>')
180 180 .addClass('widget-area')
181 181 .hide();
182 182 this.widget_area = widget_area;
183 183 var widget_prompt = $('<div/>')
184 184 .addClass('prompt')
185 185 .appendTo(widget_area);
186 186 var widget_subarea = $('<div/>')
187 187 .addClass('widget-subarea')
188 188 .appendTo(widget_area);
189 189 this.widget_subarea = widget_subarea;
190 190 var that = this;
191 191 var widget_clear_buton = $('<button />')
192 192 .addClass('close')
193 193 .html('&times;')
194 194 .click(function() {
195 195 widget_area.slideUp('', function(){
196 196 for (var i = 0; i < that.widget_views.length; i++) {
197 197 var view = that.widget_views[i];
198 198 view.remove();
199 199
200 200 // Remove widget live events.
201 201 view.off('comm:live', that._widget_live);
202 202 view.off('comm:dead', that._widget_dead);
203 203 }
204 204 that.widget_views = [];
205 205 widget_subarea.html('');
206 206 });
207 207 })
208 208 .appendTo(widget_prompt);
209 209
210 210 var output = $('<div></div>');
211 211 cell.append(input).append(widget_area).append(output);
212 212 this.element = cell;
213 213 this.output_area = new outputarea.OutputArea({
214 214 selector: output,
215 215 prompt_area: true,
216 216 events: this.events,
217 217 keyboard_manager: this.keyboard_manager});
218 218 this.completer = new completer.Completer(this, this.events);
219 219 };
220 220
221 221 /**
222 222 * Display a widget view in the cell.
223 223 */
224 224 CodeCell.prototype.display_widget_view = function(view_promise) {
225 225
226 226 // Display a dummy element
227 227 var dummy = $('<div/>');
228 228 this.widget_subarea.append(dummy);
229 229
230 230 // Display the view.
231 231 var that = this;
232 232 return view_promise.then(function(view) {
233 233 that.widget_area.show();
234 234 dummy.replaceWith(view.$el);
235 235 that.widget_views.push(view);
236 236
237 237 // Check the live state of the view's model.
238 238 if (view.model.comm_live) {
239 239 that._widget_live(view);
240 240 } else {
241 241 that._widget_dead(view);
242 242 }
243 243
244 244 // Listen to comm live events for the view.
245 245 view.on('comm:live', that._widget_live, that);
246 246 view.on('comm:dead', that._widget_dead, that);
247 247 return view;
248 248 });
249 249 };
250 250
251 251 /**
252 252 * Handles when a widget loses it's comm connection.
253 253 * @param {WidgetView} view
254 254 */
255 255 CodeCell.prototype._widget_dead = function(view) {
256 256 if (this._widgets_live) {
257 257 this._widgets_live = false;
258 258 this.widget_area.addClass('connection-problems');
259 259 }
260 260
261 261 };
262 262
263 263 /**
264 264 * Handles when a widget is connected to a live comm.
265 265 * @param {WidgetView} view
266 266 */
267 267 CodeCell.prototype._widget_live = function(view) {
268 268 if (!this._widgets_live) {
269 269 // Check that the other widgets are live too. O(N) operation.
270 270 // Abort the function at the first dead widget found.
271 271 for (var i = 0; i < this.widget_views.length; i++) {
272 272 if (!this.widget_views[i].model.comm_live) return;
273 273 }
274 274 this._widgets_live = true;
275 275 this.widget_area.removeClass('connection-problems');
276 276 }
277 277 };
278 278
279 279 /** @method bind_events */
280 280 CodeCell.prototype.bind_events = function () {
281 281 Cell.prototype.bind_events.apply(this);
282 282 var that = this;
283 283
284 284 this.element.focusout(
285 285 function() { that.auto_highlight(); }
286 286 );
287 287 };
288 288
289 289
290 290 /**
291 291 * This method gets called in CodeMirror's onKeyDown/onKeyPress
292 292 * handlers and is used to provide custom key handling. Its return
293 293 * value is used to determine if CodeMirror should ignore the event:
294 294 * true = ignore, false = don't ignore.
295 295 * @method handle_codemirror_keyevent
296 296 */
297 297
298 298 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
299 299
300 300 var that = this;
301 301 // whatever key is pressed, first, cancel the tooltip request before
302 302 // they are sent, and remove tooltip if any, except for tab again
303 303 var tooltip_closed = null;
304 304 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
305 305 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
306 306 }
307 307
308 308 var cur = editor.getCursor();
309 309 if (event.keyCode === keycodes.enter){
310 310 this.auto_highlight();
311 311 }
312 312
313 313 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
314 314 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
315 315 // browser and keyboard layout !
316 316 // Pressing '(' , request tooltip, don't forget to reappend it
317 317 // The second argument says to hide the tooltip if the docstring
318 318 // is actually empty
319 319 this.tooltip.pending(that, true);
320 320 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
321 321 // If tooltip is active, cancel it. The call to
322 322 // remove_and_cancel_tooltip above doesn't pass, force=true.
323 323 // Because of this it won't actually close the tooltip
324 324 // if it is in sticky mode. Thus, we have to check again if it is open
325 325 // and close it with force=true.
326 326 if (!this.tooltip._hidden) {
327 327 this.tooltip.remove_and_cancel_tooltip(true);
328 328 }
329 329 // If we closed the tooltip, don't let CM or the global handlers
330 330 // handle this event.
331 331 event.codemirrorIgnore = true;
332 332 event._ipkmIgnore = true;
333 333 event.preventDefault();
334 334 return true;
335 335 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
336 336 if (editor.somethingSelected() || editor.getSelections().length !== 1){
337 337 var anchor = editor.getCursor("anchor");
338 338 var head = editor.getCursor("head");
339 339 if( anchor.line !== head.line){
340 340 return false;
341 341 }
342 342 }
343 343 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
344 344 if (pre_cursor.trim() === "") {
345 345 // Don't show tooltip if the part of the line before the cursor
346 346 // is empty. In this case, let CodeMirror handle indentation.
347 347 return false;
348 348 }
349 349 this.tooltip.request(that);
350 350 event.codemirrorIgnore = true;
351 351 event.preventDefault();
352 352 return true;
353 353 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
354 354 // Tab completion.
355 355 this.tooltip.remove_and_cancel_tooltip();
356 356
357 357 // completion does not work on multicursor, it might be possible though in some cases
358 358 if (editor.somethingSelected() || editor.getSelections().length > 1) {
359 359 return false;
360 360 }
361 361 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
362 362 if (pre_cursor.trim() === "") {
363 363 // Don't autocomplete if the part of the line before the cursor
364 364 // is empty. In this case, let CodeMirror handle indentation.
365 365 return false;
366 366 } else {
367 367 event.codemirrorIgnore = true;
368 368 event.preventDefault();
369 369 this.completer.startCompletion();
370 370 return true;
371 371 }
372 372 }
373 373
374 374 // keyboard event wasn't one of those unique to code cells, let's see
375 375 // if it's one of the generic ones (i.e. check edit mode shortcuts)
376 376 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
377 377 };
378 378
379 379 // Kernel related calls.
380 380
381 381 CodeCell.prototype.set_kernel = function (kernel) {
382 382 this.kernel = kernel;
383 383 };
384 384
385 385 /**
386 386 * Execute current code cell to the kernel
387 387 * @method execute
388 388 */
389 389 CodeCell.prototype.execute = function (stop_on_error) {
390 390 if (!this.kernel || !this.kernel.is_connected()) {
391 391 console.log("Can't execute, kernel is not connected.");
392 392 return;
393 393 }
394 394
395 395 this.output_area.clear_output(false, true);
396 396
397 397 if (stop_on_error === undefined) {
398 398 stop_on_error = true;
399 399 }
400 400
401 401 // Clear widget area
402 402 for (var i = 0; i < this.widget_views.length; i++) {
403 403 var view = this.widget_views[i];
404 404 view.remove();
405 405
406 406 // Remove widget live events.
407 407 view.off('comm:live', this._widget_live);
408 408 view.off('comm:dead', this._widget_dead);
409 409 }
410 410 this.widget_views = [];
411 411 this.widget_subarea.html('');
412 412 this.widget_subarea.height('');
413 413 this.widget_area.height('');
414 414 this.widget_area.hide();
415 415
416 416 var old_msg_id = this.last_msg_id;
417 417
418 418 if (old_msg_id) {
419 419 this.kernel.clear_callbacks_for_msg(old_msg_id);
420 420 if (old_msg_id) {
421 421 delete CodeCell.msg_cells[old_msg_id];
422 422 }
423 423 }
424 424 if (this.get_text().trim().length === 0) {
425 425 // nothing to do
426 426 this.set_input_prompt(null);
427 427 return;
428 428 }
429 429 this.set_input_prompt('*');
430 430 this.element.addClass("running");
431 431 var callbacks = this.get_callbacks();
432 432
433 433 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
434 434 stop_on_error : stop_on_error});
435 435 CodeCell.msg_cells[this.last_msg_id] = this;
436 436 this.render();
437 437 this.events.trigger('execute.CodeCell', {cell: this});
438 438 };
439 439
440 440 /**
441 441 * Construct the default callbacks for
442 442 * @method get_callbacks
443 443 */
444 444 CodeCell.prototype.get_callbacks = function () {
445 445 var that = this;
446 446 return {
447 447 shell : {
448 448 reply : $.proxy(this._handle_execute_reply, this),
449 449 payload : {
450 450 set_next_input : $.proxy(this._handle_set_next_input, this),
451 451 page : $.proxy(this._open_with_pager, this)
452 452 }
453 453 },
454 454 iopub : {
455 455 output : function() {
456 456 that.output_area.handle_output.apply(that.output_area, arguments);
457 457 },
458 458 clear_output : function() {
459 459 that.output_area.handle_clear_output.apply(that.output_area, arguments);
460 460 },
461 461 },
462 462 input : $.proxy(this._handle_input_request, this)
463 463 };
464 464 };
465 465
466 466 CodeCell.prototype._open_with_pager = function (payload) {
467 467 this.events.trigger('open_with_text.Pager', payload);
468 468 };
469 469
470 470 /**
471 471 * @method _handle_execute_reply
472 472 * @private
473 473 */
474 474 CodeCell.prototype._handle_execute_reply = function (msg) {
475 475 this.set_input_prompt(msg.content.execution_count);
476 476 this.element.removeClass("running");
477 477 this.events.trigger('set_dirty.Notebook', {value: true});
478 478 };
479 479
480 480 /**
481 481 * @method _handle_set_next_input
482 482 * @private
483 483 */
484 484 CodeCell.prototype._handle_set_next_input = function (payload) {
485 485 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
486 486 this.events.trigger('set_next_input.Notebook', data);
487 487 };
488 488
489 489 /**
490 490 * @method _handle_input_request
491 491 * @private
492 492 */
493 493 CodeCell.prototype._handle_input_request = function (msg) {
494 494 this.output_area.append_raw_input(msg);
495 495 };
496 496
497 497
498 498 // Basic cell manipulation.
499 499
500 500 CodeCell.prototype.select = function () {
501 501 var cont = Cell.prototype.select.apply(this);
502 502 if (cont) {
503 503 this.code_mirror.refresh();
504 504 this.auto_highlight();
505 505 }
506 506 return cont;
507 507 };
508 508
509 509 CodeCell.prototype.render = function () {
510 510 var cont = Cell.prototype.render.apply(this);
511 511 // Always execute, even if we are already in the rendered state
512 512 return cont;
513 513 };
514 514
515 515 CodeCell.prototype.select_all = function () {
516 516 var start = {line: 0, ch: 0};
517 517 var nlines = this.code_mirror.lineCount();
518 518 var last_line = this.code_mirror.getLine(nlines-1);
519 519 var end = {line: nlines-1, ch: last_line.length};
520 520 this.code_mirror.setSelection(start, end);
521 521 };
522 522
523 523
524 524 CodeCell.prototype.collapse_output = function () {
525 525 this.output_area.collapse();
526 526 };
527 527
528 528
529 529 CodeCell.prototype.expand_output = function () {
530 530 this.output_area.expand();
531 531 this.output_area.unscroll_area();
532 532 };
533 533
534 534 CodeCell.prototype.scroll_output = function () {
535 535 this.output_area.expand();
536 536 this.output_area.scroll_if_long();
537 537 };
538 538
539 539 CodeCell.prototype.toggle_output = function () {
540 540 this.output_area.toggle_output();
541 541 };
542 542
543 543 CodeCell.prototype.toggle_output_scroll = function () {
544 544 this.output_area.toggle_scroll();
545 545 };
546 546
547 547
548 548 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
549 549 var ns;
550 550 if (prompt_value === undefined || prompt_value === null) {
551 551 ns = "&nbsp;";
552 552 } else {
553 553 ns = encodeURIComponent(prompt_value);
554 554 }
555 555 return 'In&nbsp;[' + ns + ']:';
556 556 };
557 557
558 558 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
559 559 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
560 560 for(var i=1; i < lines_number; i++) {
561 561 html.push(['...:']);
562 562 }
563 563 return html.join('<br/>');
564 564 };
565 565
566 566 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
567 567
568 568
569 569 CodeCell.prototype.set_input_prompt = function (number) {
570 570 var nline = 1;
571 571 if (this.code_mirror !== undefined) {
572 572 nline = this.code_mirror.lineCount();
573 573 }
574 574 this.input_prompt_number = number;
575 575 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
576 576 // This HTML call is okay because the user contents are escaped.
577 577 this.element.find('div.input_prompt').html(prompt_html);
578 578 };
579 579
580 580
581 581 CodeCell.prototype.clear_input = function () {
582 582 this.code_mirror.setValue('');
583 583 };
584 584
585 585
586 586 CodeCell.prototype.get_text = function () {
587 587 return this.code_mirror.getValue();
588 588 };
589 589
590 590
591 591 CodeCell.prototype.set_text = function (code) {
592 592 return this.code_mirror.setValue(code);
593 593 };
594 594
595 595
596 596 CodeCell.prototype.clear_output = function (wait) {
597 597 this.output_area.clear_output(wait);
598 598 this.set_input_prompt();
599 599 };
600 600
601 601
602 602 // JSON serialization
603 603
604 604 CodeCell.prototype.fromJSON = function (data) {
605 605 Cell.prototype.fromJSON.apply(this, arguments);
606 606 if (data.cell_type === 'code') {
607 607 if (data.source !== undefined) {
608 608 this.set_text(data.source);
609 609 // make this value the starting point, so that we can only undo
610 610 // to this state, instead of a blank cell
611 611 this.code_mirror.clearHistory();
612 612 this.auto_highlight();
613 613 }
614 614 this.set_input_prompt(data.execution_count);
615 615 this.output_area.trusted = data.metadata.trusted || false;
616 616 this.output_area.fromJSON(data.outputs, data.metadata);
617 617 }
618 618 };
619 619
620 620
621 621 CodeCell.prototype.toJSON = function () {
622 622 var data = Cell.prototype.toJSON.apply(this);
623 623 data.source = this.get_text();
624 624 // is finite protect against undefined and '*' value
625 625 if (isFinite(this.input_prompt_number)) {
626 626 data.execution_count = this.input_prompt_number;
627 627 } else {
628 628 data.execution_count = null;
629 629 }
630 630 var outputs = this.output_area.toJSON();
631 631 data.outputs = outputs;
632 632 data.metadata.trusted = this.output_area.trusted;
633 633 data.metadata.collapsed = this.output_area.collapsed;
634 634 if (this.output_area.scroll_state === 'auto') {
635 635 delete data.metadata.scrolled;
636 636 } else {
637 637 data.metadata.scrolled = this.output_area.scroll_state;
638 638 }
639 639 return data;
640 640 };
641 641
642 642 /**
643 643 * handle cell level logic when a cell is unselected
644 644 * @method unselect
645 645 * @return is the action being taken
646 646 */
647 647 CodeCell.prototype.unselect = function () {
648 648 var cont = Cell.prototype.unselect.apply(this);
649 649 if (cont) {
650 650 // When a code cell is usnelected, make sure that the corresponding
651 651 // tooltip and completer to that cell is closed.
652 652 this.tooltip.remove_and_cancel_tooltip(true);
653 653 if (this.completer !== null) {
654 654 this.completer.close();
655 655 }
656 656 }
657 657 return cont;
658 658 };
659 659
660 660 // Backwards compatability.
661 661 IPython.CodeCell = CodeCell;
662 662
663 663 return {'CodeCell': CodeCell};
664 664 });
@@ -1,375 +1,374 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'base/js/utils',
7 7 'jquery',
8 8 'notebook/js/cell',
9 9 'base/js/security',
10 10 'services/config',
11 11 'notebook/js/mathjaxutils',
12 12 'notebook/js/celltoolbar',
13 13 'components/marked/lib/marked',
14 14 'codemirror/lib/codemirror',
15 15 'codemirror/mode/gfm/gfm',
16 16 'notebook/js/codemirror-ipythongfm'
17 17 ], function(IPython,
18 18 utils,
19 19 $,
20 20 cell,
21 21 security,
22 22 configmod,
23 23 mathjaxutils,
24 24 celltoolbar,
25 25 marked,
26 26 CodeMirror,
27 27 gfm,
28 28 ipgfm
29 29 ) {
30 30 "use strict";
31 31 var Cell = cell.Cell;
32 32
33 33 var TextCell = function (options) {
34 34 /**
35 35 * Constructor
36 36 *
37 37 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
38 38 * and cell type is 'text' cell start as not redered.
39 39 *
40 40 * Parameters:
41 41 * options: dictionary
42 42 * Dictionary of keyword arguments.
43 43 * events: $(Events) instance
44 44 * config: dictionary
45 45 * keyboard_manager: KeyboardManager instance
46 46 * notebook: Notebook instance
47 47 */
48 48 options = options || {};
49 49
50 50 // in all TextCell/Cell subclasses
51 51 // do not assign most of members here, just pass it down
52 52 // in the options dict potentially overwriting what you wish.
53 53 // they will be assigned in the base class.
54 54 this.notebook = options.notebook;
55 55 this.events = options.events;
56 56 this.config = options.config;
57 57
58 58 // we cannot put this as a class key as it has handle to "this".
59 59 var config = utils.mergeopt(TextCell, this.config);
60 60 Cell.apply(this, [{
61 61 config: config,
62 62 keyboard_manager: options.keyboard_manager,
63 63 events: this.events}]);
64 64
65 65 this.cell_type = this.cell_type || 'text';
66 66 mathjaxutils = mathjaxutils;
67 67 this.rendered = false;
68 68 };
69 69
70 70 TextCell.prototype = Object.create(Cell.prototype);
71 71
72 72 TextCell.options_default = {
73 73 cm_config : {
74 74 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
75 75 mode: 'htmlmixed',
76 76 lineWrapping : true,
77 77 }
78 78 };
79 79
80 80
81 81 /**
82 82 * Create the DOM element of the TextCell
83 83 * @method create_element
84 84 * @private
85 85 */
86 86 TextCell.prototype.create_element = function () {
87 87 Cell.prototype.create_element.apply(this, arguments);
88 88 var that = this;
89 89
90 90 var cell = $("<div>").addClass('cell text_cell');
91 91 cell.attr('tabindex','2');
92 92
93 93 var prompt = $('<div/>').addClass('prompt input_prompt');
94 94 cell.append(prompt);
95 95 var inner_cell = $('<div/>').addClass('inner_cell');
96 96 this.celltoolbar = new celltoolbar.CellToolbar({
97 97 cell: this,
98 98 notebook: this.notebook});
99 99 inner_cell.append(this.celltoolbar.element);
100 100 var input_area = $('<div/>').addClass('input_area');
101 101 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
102 102 // In case of bugs that put the keyboard manager into an inconsistent state,
103 103 // ensure KM is enabled when CodeMirror is focused:
104 104 this.code_mirror.on('focus', function () {
105 105 if (that.keyboard_manager) {
106 106 that.keyboard_manager.enable();
107 107 }
108 108 });
109 109 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
110 110 // The tabindex=-1 makes this div focusable.
111 111 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
112 112 .attr('tabindex','-1');
113 113 inner_cell.append(input_area).append(render_area);
114 114 cell.append(inner_cell);
115 115 this.element = cell;
116 116 };
117 117
118 118
119 119 // Cell level actions
120 120
121 121 TextCell.prototype.select = function () {
122 122 var cont = Cell.prototype.select.apply(this);
123 123 if (cont) {
124 124 if (this.mode === 'edit') {
125 125 this.code_mirror.refresh();
126 126 }
127 127 }
128 128 return cont;
129 129 };
130 130
131 131 TextCell.prototype.unrender = function () {
132 if (this.read_only) return;
133 132 var cont = Cell.prototype.unrender.apply(this);
134 133 if (cont) {
135 134 var text_cell = this.element;
136 135 if (this.get_text() === this.placeholder) {
137 136 this.set_text('');
138 137 }
139 138 this.refresh();
140 139 }
141 140 return cont;
142 141 };
143 142
144 143 TextCell.prototype.execute = function () {
145 144 this.render();
146 145 };
147 146
148 147 /**
149 148 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
150 149 * @method get_text
151 150 * @retrun {string} CodeMirror current text value
152 151 */
153 152 TextCell.prototype.get_text = function() {
154 153 return this.code_mirror.getValue();
155 154 };
156 155
157 156 /**
158 157 * @param {string} text - Codemiror text value
159 158 * @see TextCell#get_text
160 159 * @method set_text
161 160 * */
162 161 TextCell.prototype.set_text = function(text) {
163 162 this.code_mirror.setValue(text);
164 163 this.unrender();
165 164 this.code_mirror.refresh();
166 165 };
167 166
168 167 /**
169 168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
170 169 * @method get_rendered
171 170 * */
172 171 TextCell.prototype.get_rendered = function() {
173 172 return this.element.find('div.text_cell_render').html();
174 173 };
175 174
176 175 /**
177 176 * @method set_rendered
178 177 */
179 178 TextCell.prototype.set_rendered = function(text) {
180 179 this.element.find('div.text_cell_render').html(text);
181 180 };
182 181
183 182
184 183 /**
185 184 * Create Text cell from JSON
186 185 * @param {json} data - JSON serialized text-cell
187 186 * @method fromJSON
188 187 */
189 188 TextCell.prototype.fromJSON = function (data) {
190 189 Cell.prototype.fromJSON.apply(this, arguments);
191 190 if (data.cell_type === this.cell_type) {
192 191 if (data.source !== undefined) {
193 192 this.set_text(data.source);
194 193 // make this value the starting point, so that we can only undo
195 194 // to this state, instead of a blank cell
196 195 this.code_mirror.clearHistory();
197 196 // TODO: This HTML needs to be treated as potentially dangerous
198 197 // user input and should be handled before set_rendered.
199 198 this.set_rendered(data.rendered || '');
200 199 this.rendered = false;
201 200 this.render();
202 201 }
203 202 }
204 203 };
205 204
206 205 /** Generate JSON from cell
207 206 * @return {object} cell data serialised to json
208 207 */
209 208 TextCell.prototype.toJSON = function () {
210 209 var data = Cell.prototype.toJSON.apply(this);
211 210 data.source = this.get_text();
212 211 if (data.source == this.placeholder) {
213 212 data.source = "";
214 213 }
215 214 return data;
216 215 };
217 216
218 217
219 218 var MarkdownCell = function (options) {
220 219 /**
221 220 * Constructor
222 221 *
223 222 * Parameters:
224 223 * options: dictionary
225 224 * Dictionary of keyword arguments.
226 225 * events: $(Events) instance
227 226 * config: ConfigSection instance
228 227 * keyboard_manager: KeyboardManager instance
229 228 * notebook: Notebook instance
230 229 */
231 230 options = options || {};
232 231 var config = utils.mergeopt(MarkdownCell, {});
233 TextCell.apply(this, [$.extend({}, options, {config: config})]);
234
235 232 this.class_config = new configmod.ConfigWithDefaults(options.config,
236 233 {}, 'MarkdownCell');
234 TextCell.apply(this, [$.extend({}, options, {config: config})]);
235
237 236 this.cell_type = 'markdown';
238 237 };
239 238
240 239 MarkdownCell.options_default = {
241 240 cm_config: {
242 241 mode: 'ipythongfm'
243 242 },
244 243 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
245 244 };
246 245
247 246 MarkdownCell.prototype = Object.create(TextCell.prototype);
248 247
249 248 MarkdownCell.prototype.set_heading_level = function (level) {
250 249 /**
251 250 * make a markdown cell a heading
252 251 */
253 252 level = level || 1;
254 253 var source = this.get_text();
255 254 source = source.replace(/^(#*)\s?/,
256 255 new Array(level + 1).join('#') + ' ');
257 256 this.set_text(source);
258 257 this.refresh();
259 258 if (this.rendered) {
260 259 this.render();
261 260 }
262 261 };
263 262
264 263 /**
265 264 * @method render
266 265 */
267 266 MarkdownCell.prototype.render = function () {
268 267 var cont = TextCell.prototype.render.apply(this);
269 268 if (cont) {
270 269 var that = this;
271 270 var text = this.get_text();
272 271 var math = null;
273 272 if (text === "") { text = this.placeholder; }
274 273 var text_and_math = mathjaxutils.remove_math(text);
275 274 text = text_and_math[0];
276 275 math = text_and_math[1];
277 276 marked(text, function (err, html) {
278 277 html = mathjaxutils.replace_math(html, math);
279 278 html = security.sanitize_html(html);
280 279 html = $($.parseHTML(html));
281 280 // add anchors to headings
282 281 html.find(":header").addBack(":header").each(function (i, h) {
283 282 h = $(h);
284 283 var hash = h.text().replace(/ /g, '-');
285 284 h.attr('id', hash);
286 285 h.append(
287 286 $('<a/>')
288 287 .addClass('anchor-link')
289 288 .attr('href', '#' + hash)
290 289 .text('ΒΆ')
291 290 );
292 291 });
293 292 // links in markdown cells should open in new tabs
294 293 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
295 294 that.set_rendered(html);
296 295 that.typeset();
297 296 that.events.trigger("rendered.MarkdownCell", {cell: that});
298 297 });
299 298 }
300 299 return cont;
301 300 };
302 301
303 302
304 303 var RawCell = function (options) {
305 304 /**
306 305 * Constructor
307 306 *
308 307 * Parameters:
309 308 * options: dictionary
310 309 * Dictionary of keyword arguments.
311 310 * events: $(Events) instance
312 311 * config: ConfigSection instance
313 312 * keyboard_manager: KeyboardManager instance
314 313 * notebook: Notebook instance
315 314 */
316 315 options = options || {};
317 316 var config = utils.mergeopt(RawCell, {});
318 317 TextCell.apply(this, [$.extend({}, options, {config: config})]);
319 318
320 319 this.class_config = new configmod.ConfigWithDefaults(options.config,
321 320 RawCell.config_defaults, 'RawCell');
322 321 this.cell_type = 'raw';
323 322 };
324 323
325 324 RawCell.options_default = {
326 325 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
327 326 "It will not be rendered in the notebook. " +
328 327 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
329 328 };
330 329
331 330 RawCell.config_defaults = {
332 331 highlight_modes : {
333 332 'diff' :{'reg':[/^diff/]}
334 333 },
335 334 };
336 335
337 336 RawCell.prototype = Object.create(TextCell.prototype);
338 337
339 338 /** @method bind_events **/
340 339 RawCell.prototype.bind_events = function () {
341 340 TextCell.prototype.bind_events.apply(this);
342 341 var that = this;
343 342 this.element.focusout(function() {
344 343 that.auto_highlight();
345 344 that.render();
346 345 });
347 346
348 347 this.code_mirror.on('focus', function() { that.unrender(); });
349 348 };
350 349
351 350 /** @method render **/
352 351 RawCell.prototype.render = function () {
353 352 var cont = TextCell.prototype.render.apply(this);
354 353 if (cont){
355 354 var text = this.get_text();
356 355 if (text === "") { text = this.placeholder; }
357 356 this.set_text(text);
358 357 this.element.removeClass('rendered');
359 358 this.auto_highlight();
360 359 }
361 360 return cont;
362 361 };
363 362
364 363 // Backwards compatability.
365 364 IPython.TextCell = TextCell;
366 365 IPython.MarkdownCell = MarkdownCell;
367 366 IPython.RawCell = RawCell;
368 367
369 368 var textcell = {
370 369 TextCell: TextCell,
371 370 MarkdownCell: MarkdownCell,
372 371 RawCell: RawCell
373 372 };
374 373 return textcell;
375 374 });
General Comments 0
You need to be logged in to leave comments. Login now