##// END OF EJS Templates
remove Stray .c
Matthias Bussonnier -
Show More
@@ -1,711 +1,711
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 // is enabled.c
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 59 this.selected = false;
60 60 this.rendered = false;
61 61 this.mode = 'command';
62 62
63 63 // Metadata property
64 64 var that = this;
65 65 this._metadata = {};
66 66 Object.defineProperty(this, 'metadata', {
67 67 get: function() { return that._metadata; },
68 68 set: function(value) {
69 69 that._metadata = value;
70 70 if (that.celltoolbar) {
71 71 that.celltoolbar.rebuild();
72 72 }
73 73 }
74 74 });
75 75
76 76 // load this from metadata later ?
77 77 this.user_highlight = 'auto';
78 78
79 79 var _local_cm_config = {};
80 80 if(this.class_config){
81 81 _local_cm_config = this.class_config.get_sync('cm_config');
82 82 }
83 83 this.cm_config = utils.mergeopt({}, config.cm_config, _local_cm_config);
84 84 this.cell_id = utils.uuid();
85 85 this._options = config;
86 86
87 87 // For JS VM engines optimization, attributes should be all set (even
88 88 // to null) in the constructor, and if possible, if different subclass
89 89 // have new attributes with same name, they should be created in the
90 90 // same order. Easiest is to create and set to null in parent class.
91 91
92 92 this.element = null;
93 93 this.cell_type = this.cell_type || null;
94 94 this.code_mirror = null;
95 95
96 96 this.create_element();
97 97 if (this.element !== null) {
98 98 this.element.data("cell", this);
99 99 this.bind_events();
100 100 this.init_classes();
101 101 }
102 102 };
103 103
104 104 Cell.options_default = {
105 105 cm_config : {
106 106 indentUnit : 4,
107 107 readOnly: false,
108 108 theme: "default",
109 109 extraKeys: {
110 110 "Cmd-Right":"goLineRight",
111 111 "End":"goLineRight",
112 112 "Cmd-Left":"goLineLeft"
113 113 }
114 114 }
115 115 };
116 116
117 117 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
118 118 // by disabling drag/drop altogether on Safari
119 119 // https://github.com/codemirror/CodeMirror/issues/332
120 120 if (utils.browser[0] == "Safari") {
121 121 Cell.options_default.cm_config.dragDrop = false;
122 122 }
123 123
124 124 /**
125 125 * Empty. Subclasses must implement create_element.
126 126 * This should contain all the code to create the DOM element in notebook
127 127 * and will be called by Base Class constructor.
128 128 * @method create_element
129 129 */
130 130 Cell.prototype.create_element = function () {
131 131 };
132 132
133 133 Cell.prototype.init_classes = function () {
134 134 /**
135 135 * Call after this.element exists to initialize the css classes
136 136 * related to selected, rendered and mode.
137 137 */
138 138 if (this.selected) {
139 139 this.element.addClass('selected');
140 140 } else {
141 141 this.element.addClass('unselected');
142 142 }
143 143 if (this.rendered) {
144 144 this.element.addClass('rendered');
145 145 } else {
146 146 this.element.addClass('unrendered');
147 147 }
148 148 };
149 149
150 150 /**
151 151 * Subclasses can implement override bind_events.
152 152 * Be carefull to call the parent method when overwriting as it fires event.
153 153 * this will be triggerd after create_element in constructor.
154 154 * @method bind_events
155 155 */
156 156 Cell.prototype.bind_events = function () {
157 157 var that = this;
158 158 // We trigger events so that Cell doesn't have to depend on Notebook.
159 159 that.element.click(function (event) {
160 160 if (!that.selected) {
161 161 that.events.trigger('select.Cell', {'cell':that});
162 162 }
163 163 });
164 164 that.element.focusin(function (event) {
165 165 if (!that.selected) {
166 166 that.events.trigger('select.Cell', {'cell':that});
167 167 }
168 168 });
169 169 if (this.code_mirror) {
170 170 this.code_mirror.on("change", function(cm, change) {
171 171 that.events.trigger("set_dirty.Notebook", {value: true});
172 172 });
173 173 }
174 174 if (this.code_mirror) {
175 175 this.code_mirror.on('focus', function(cm, change) {
176 176 that.events.trigger('edit_mode.Cell', {cell: that});
177 177 });
178 178 }
179 179 if (this.code_mirror) {
180 180 this.code_mirror.on('blur', function(cm, change) {
181 181 that.events.trigger('command_mode.Cell', {cell: that});
182 182 });
183 183 }
184 184
185 185 this.element.dblclick(function () {
186 186 if (that.selected === false) {
187 187 this.events.trigger('select.Cell', {'cell':that});
188 188 }
189 189 var cont = that.unrender();
190 190 if (cont) {
191 191 that.focus_editor();
192 192 }
193 193 });
194 194 };
195 195
196 196 /**
197 197 * This method gets called in CodeMirror's onKeyDown/onKeyPress
198 198 * handlers and is used to provide custom key handling.
199 199 *
200 200 * To have custom handling, subclasses should override this method, but still call it
201 201 * in order to process the Edit mode keyboard shortcuts.
202 202 *
203 203 * @method handle_codemirror_keyevent
204 204 * @param {CodeMirror} editor - The codemirror instance bound to the cell
205 205 * @param {event} event - key press event which either should or should not be handled by CodeMirror
206 206 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
207 207 */
208 208 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
209 209 var shortcuts = this.keyboard_manager.edit_shortcuts;
210 210
211 211 var cur = editor.getCursor();
212 212 if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
213 213 event._ipkmIgnore = true;
214 214 }
215 215 var nLastLine = editor.lastLine();
216 216 if ((event.keyCode === 40) &&
217 217 ((cur.line !== nLastLine) ||
218 218 (cur.ch !== editor.getLineHandle(nLastLine).text.length))
219 219 ) {
220 220 event._ipkmIgnore = true;
221 221 }
222 222 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
223 223 // manager will handle it
224 224 if (shortcuts.handles(event)) {
225 225 return true;
226 226 }
227 227
228 228 return false;
229 229 };
230 230
231 231
232 232 /**
233 233 * Triger typsetting of math by mathjax on current cell element
234 234 * @method typeset
235 235 */
236 236 Cell.prototype.typeset = function () {
237 237 utils.typeset(this.element);
238 238 };
239 239
240 240 /**
241 241 * handle cell level logic when a cell is selected
242 242 * @method select
243 243 * @return is the action being taken
244 244 */
245 245 Cell.prototype.select = function () {
246 246 if (!this.selected) {
247 247 this.element.addClass('selected');
248 248 this.element.removeClass('unselected');
249 249 this.selected = true;
250 250 return true;
251 251 } else {
252 252 return false;
253 253 }
254 254 };
255 255
256 256 /**
257 257 * handle cell level logic when a cell is unselected
258 258 * @method unselect
259 259 * @return is the action being taken
260 260 */
261 261 Cell.prototype.unselect = function () {
262 262 if (this.selected) {
263 263 this.element.addClass('unselected');
264 264 this.element.removeClass('selected');
265 265 this.selected = false;
266 266 return true;
267 267 } else {
268 268 return false;
269 269 }
270 270 };
271 271
272 272 /**
273 273 * should be overritten by subclass
274 274 * @method execute
275 275 */
276 276 Cell.prototype.execute = function () {
277 277 return;
278 278 };
279 279
280 280 /**
281 281 * handle cell level logic when a cell is rendered
282 282 * @method render
283 283 * @return is the action being taken
284 284 */
285 285 Cell.prototype.render = function () {
286 286 if (!this.rendered) {
287 287 this.element.addClass('rendered');
288 288 this.element.removeClass('unrendered');
289 289 this.rendered = true;
290 290 return true;
291 291 } else {
292 292 return false;
293 293 }
294 294 };
295 295
296 296 /**
297 297 * handle cell level logic when a cell is unrendered
298 298 * @method unrender
299 299 * @return is the action being taken
300 300 */
301 301 Cell.prototype.unrender = function () {
302 302 if (this.rendered) {
303 303 this.element.addClass('unrendered');
304 304 this.element.removeClass('rendered');
305 305 this.rendered = false;
306 306 return true;
307 307 } else {
308 308 return false;
309 309 }
310 310 };
311 311
312 312 /**
313 313 * Delegates keyboard shortcut handling to either IPython keyboard
314 314 * manager when in command mode, or CodeMirror when in edit mode
315 315 *
316 316 * @method handle_keyevent
317 317 * @param {CodeMirror} editor - The codemirror instance bound to the cell
318 318 * @param {event} - key event to be handled
319 319 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
320 320 */
321 321 Cell.prototype.handle_keyevent = function (editor, event) {
322 322 if (this.mode === 'command') {
323 323 return true;
324 324 } else if (this.mode === 'edit') {
325 325 return this.handle_codemirror_keyevent(editor, event);
326 326 }
327 327 };
328 328
329 329 /**
330 330 * @method at_top
331 331 * @return {Boolean}
332 332 */
333 333 Cell.prototype.at_top = function () {
334 334 var cm = this.code_mirror;
335 335 var cursor = cm.getCursor();
336 336 if (cursor.line === 0 && cursor.ch === 0) {
337 337 return true;
338 338 }
339 339 return false;
340 340 };
341 341
342 342 /**
343 343 * @method at_bottom
344 344 * @return {Boolean}
345 345 * */
346 346 Cell.prototype.at_bottom = function () {
347 347 var cm = this.code_mirror;
348 348 var cursor = cm.getCursor();
349 349 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
350 350 return true;
351 351 }
352 352 return false;
353 353 };
354 354
355 355 /**
356 356 * enter the command mode for the cell
357 357 * @method command_mode
358 358 * @return is the action being taken
359 359 */
360 360 Cell.prototype.command_mode = function () {
361 361 if (this.mode !== 'command') {
362 362 this.mode = 'command';
363 363 return true;
364 364 } else {
365 365 return false;
366 366 }
367 367 };
368 368
369 369 /**
370 370 * enter the edit mode for the cell
371 371 * @method command_mode
372 372 * @return is the action being taken
373 373 */
374 374 Cell.prototype.edit_mode = function () {
375 375 if (this.mode !== 'edit') {
376 376 this.mode = 'edit';
377 377 return true;
378 378 } else {
379 379 return false;
380 380 }
381 381 };
382 382
383 383 Cell.prototype.ensure_focused = function() {
384 384 if(this.element !== document.activeElement && !this.code_mirror.hasFocus()){
385 385 this.focus_cell();
386 386 }
387 387 }
388 388
389 389 /**
390 390 * Focus the cell in the DOM sense
391 391 * @method focus_cell
392 392 */
393 393 Cell.prototype.focus_cell = function () {
394 394 this.element.focus();
395 395 };
396 396
397 397 /**
398 398 * Focus the editor area so a user can type
399 399 *
400 400 * NOTE: If codemirror is focused via a mouse click event, you don't want to
401 401 * call this because it will cause a page jump.
402 402 * @method focus_editor
403 403 */
404 404 Cell.prototype.focus_editor = function () {
405 405 this.refresh();
406 406 this.code_mirror.focus();
407 407 };
408 408
409 409 /**
410 410 * Refresh codemirror instance
411 411 * @method refresh
412 412 */
413 413 Cell.prototype.refresh = function () {
414 414 if (this.code_mirror) {
415 415 this.code_mirror.refresh();
416 416 }
417 417 };
418 418
419 419 /**
420 420 * should be overritten by subclass
421 421 * @method get_text
422 422 */
423 423 Cell.prototype.get_text = function () {
424 424 };
425 425
426 426 /**
427 427 * should be overritten by subclass
428 428 * @method set_text
429 429 * @param {string} text
430 430 */
431 431 Cell.prototype.set_text = function (text) {
432 432 };
433 433
434 434 /**
435 435 * should be overritten by subclass
436 436 * serialise cell to json.
437 437 * @method toJSON
438 438 **/
439 439 Cell.prototype.toJSON = function () {
440 440 var data = {};
441 441 // deepcopy the metadata so copied cells don't share the same object
442 442 data.metadata = JSON.parse(JSON.stringify(this.metadata));
443 443 data.cell_type = this.cell_type;
444 444 return data;
445 445 };
446 446
447 447 /**
448 448 * should be overritten by subclass
449 449 * @method fromJSON
450 450 **/
451 451 Cell.prototype.fromJSON = function (data) {
452 452 if (data.metadata !== undefined) {
453 453 this.metadata = data.metadata;
454 454 }
455 455 };
456 456
457 457
458 458 /**
459 459 * can the cell be split into two cells (false if not deletable)
460 460 * @method is_splittable
461 461 **/
462 462 Cell.prototype.is_splittable = function () {
463 463 return this.is_deletable();
464 464 };
465 465
466 466
467 467 /**
468 468 * can the cell be merged with other cells (false if not deletable)
469 469 * @method is_mergeable
470 470 **/
471 471 Cell.prototype.is_mergeable = function () {
472 472 return this.is_deletable();
473 473 };
474 474
475 475 /**
476 476 * is the cell deletable? only false (undeletable) if
477 477 * metadata.deletable is explicitly false -- everything else
478 478 * counts as true
479 479 *
480 480 * @method is_deletable
481 481 **/
482 482 Cell.prototype.is_deletable = function () {
483 483 if (this.metadata.deletable === false) {
484 484 return false;
485 485 }
486 486 return true;
487 487 };
488 488
489 489 /**
490 490 * @return {String} - the text before the cursor
491 491 * @method get_pre_cursor
492 492 **/
493 493 Cell.prototype.get_pre_cursor = function () {
494 494 var cursor = this.code_mirror.getCursor();
495 495 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
496 496 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
497 497 return text;
498 498 };
499 499
500 500
501 501 /**
502 502 * @return {String} - the text after the cursor
503 503 * @method get_post_cursor
504 504 **/
505 505 Cell.prototype.get_post_cursor = function () {
506 506 var cursor = this.code_mirror.getCursor();
507 507 var last_line_num = this.code_mirror.lineCount()-1;
508 508 var last_line_len = this.code_mirror.getLine(last_line_num).length;
509 509 var end = {line:last_line_num, ch:last_line_len};
510 510 var text = this.code_mirror.getRange(cursor, end);
511 511 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
512 512 return text;
513 513 };
514 514
515 515 /**
516 516 * Show/Hide CodeMirror LineNumber
517 517 * @method show_line_numbers
518 518 *
519 519 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
520 520 **/
521 521 Cell.prototype.show_line_numbers = function (value) {
522 522 this.code_mirror.setOption('lineNumbers', value);
523 523 this.code_mirror.refresh();
524 524 };
525 525
526 526 /**
527 527 * Toggle CodeMirror LineNumber
528 528 * @method toggle_line_numbers
529 529 **/
530 530 Cell.prototype.toggle_line_numbers = function () {
531 531 var val = this.code_mirror.getOption('lineNumbers');
532 532 this.show_line_numbers(!val);
533 533 };
534 534
535 535 /**
536 536 * Force codemirror highlight mode
537 537 * @method force_highlight
538 538 * @param {object} - CodeMirror mode
539 539 **/
540 540 Cell.prototype.force_highlight = function(mode) {
541 541 this.user_highlight = mode;
542 542 this.auto_highlight();
543 543 };
544 544
545 545 /**
546 546 * Trigger autodetection of highlight scheme for current cell
547 547 * @method auto_highlight
548 548 */
549 549 Cell.prototype.auto_highlight = function () {
550 550 this._auto_highlight(this.class_config.get_sync('highlight_modes'));
551 551 };
552 552
553 553 /**
554 554 * Try to autodetect cell highlight mode, or use selected mode
555 555 * @methods _auto_highlight
556 556 * @private
557 557 * @param {String|object|undefined} - CodeMirror mode | 'auto'
558 558 **/
559 559 Cell.prototype._auto_highlight = function (modes) {
560 560 /**
561 561 *Here we handle manually selected modes
562 562 */
563 563 var that = this;
564 564 var mode;
565 565 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
566 566 {
567 567 mode = this.user_highlight;
568 568 CodeMirror.autoLoadMode(this.code_mirror, mode);
569 569 this.code_mirror.setOption('mode', mode);
570 570 return;
571 571 }
572 572 var current_mode = this.code_mirror.getOption('mode', mode);
573 573 var first_line = this.code_mirror.getLine(0);
574 574 // loop on every pairs
575 575 for(mode in modes) {
576 576 var regs = modes[mode].reg;
577 577 // only one key every time but regexp can't be keys...
578 578 for(var i=0; i<regs.length; i++) {
579 579 // here we handle non magic_modes.
580 580 // TODO :
581 581 // On 3.0 and below, these things were regex.
582 582 // But now should be string for json-able config.
583 583 // We should get rid of assuming they might be already
584 584 // in a later version of IPython.
585 585 var re = regs[i];
586 586 if(typeof(re) === 'string'){
587 587 re = new RegExp(re)
588 588 }
589 589 if(first_line.match(re) !== null) {
590 590 if(current_mode == mode){
591 591 return;
592 592 }
593 593 if (mode.search('magic_') !== 0) {
594 594 utils.requireCodeMirrorMode(mode, function (spec) {
595 595 that.code_mirror.setOption('mode', spec);
596 596 });
597 597 return;
598 598 }
599 599 var open = modes[mode].open || "%%";
600 600 var close = modes[mode].close || "%%end";
601 601 var magic_mode = mode;
602 602 mode = magic_mode.substr(6);
603 603 if(current_mode == magic_mode){
604 604 return;
605 605 }
606 606 utils.requireCodeMirrorMode(mode, function (spec) {
607 607 // create on the fly a mode that switch between
608 608 // plain/text and something else, otherwise `%%` is
609 609 // source of some highlight issues.
610 610 CodeMirror.defineMode(magic_mode, function(config) {
611 611 return CodeMirror.multiplexingMode(
612 612 CodeMirror.getMode(config, 'text/plain'),
613 613 // always set something on close
614 614 {open: open, close: close,
615 615 mode: CodeMirror.getMode(config, spec),
616 616 delimStyle: "delimit"
617 617 }
618 618 );
619 619 });
620 620 that.code_mirror.setOption('mode', magic_mode);
621 621 });
622 622 return;
623 623 }
624 624 }
625 625 }
626 626 // fallback on default
627 627 var default_mode;
628 628 try {
629 629 default_mode = this._options.cm_config.mode;
630 630 } catch(e) {
631 631 default_mode = 'text/plain';
632 632 }
633 633 if( current_mode === default_mode){
634 634 return;
635 635 }
636 636 this.code_mirror.setOption('mode', default_mode);
637 637 };
638 638
639 639 var UnrecognizedCell = function (options) {
640 640 /** Constructor for unrecognized cells */
641 641 Cell.apply(this, arguments);
642 642 this.cell_type = 'unrecognized';
643 643 this.celltoolbar = null;
644 644 this.data = {};
645 645
646 646 Object.seal(this);
647 647 };
648 648
649 649 UnrecognizedCell.prototype = Object.create(Cell.prototype);
650 650
651 651
652 652 // cannot merge or split unrecognized cells
653 653 UnrecognizedCell.prototype.is_mergeable = function () {
654 654 return false;
655 655 };
656 656
657 657 UnrecognizedCell.prototype.is_splittable = function () {
658 658 return false;
659 659 };
660 660
661 661 UnrecognizedCell.prototype.toJSON = function () {
662 662 /**
663 663 * deepcopy the metadata so copied cells don't share the same object
664 664 */
665 665 return JSON.parse(JSON.stringify(this.data));
666 666 };
667 667
668 668 UnrecognizedCell.prototype.fromJSON = function (data) {
669 669 this.data = data;
670 670 if (data.metadata !== undefined) {
671 671 this.metadata = data.metadata;
672 672 } else {
673 673 data.metadata = this.metadata;
674 674 }
675 675 this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
676 676 };
677 677
678 678 UnrecognizedCell.prototype.create_element = function () {
679 679 Cell.prototype.create_element.apply(this, arguments);
680 680 var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
681 681 cell.attr('tabindex','2');
682 682
683 683 var prompt = $('<div/>').addClass('prompt input_prompt');
684 684 cell.append(prompt);
685 685 var inner_cell = $('<div/>').addClass('inner_cell');
686 686 inner_cell.append(
687 687 $("<a>")
688 688 .attr("href", "#")
689 689 .text("Unrecognized cell type")
690 690 );
691 691 cell.append(inner_cell);
692 692 this.element = cell;
693 693 };
694 694
695 695 UnrecognizedCell.prototype.bind_events = function () {
696 696 Cell.prototype.bind_events.apply(this, arguments);
697 697 var cell = this;
698 698
699 699 this.element.find('.inner_cell').find("a").click(function () {
700 700 cell.events.trigger('unrecognized_cell.Cell', {cell: cell});
701 701 });
702 702 };
703 703
704 704 // Backwards compatibility.
705 705 IPython.Cell = Cell;
706 706
707 707 return {
708 708 Cell: Cell,
709 709 UnrecognizedCell: UnrecognizedCell
710 710 };
711 711 });
General Comments 0
You need to be logged in to leave comments. Login now