##// END OF EJS Templates
no need to special-case Escape anymore
Paul Ivanov -
Show More
@@ -1,579 +1,566 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 // Cell
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 Cell
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19 "use strict";
20 20
21 21 var utils = IPython.utils;
22 22 var keycodes = IPython.keyboard.keycodes;
23 23
24 24 /**
25 25 * The Base `Cell` class from which to inherit
26 26 * @class Cell
27 27 **/
28 28
29 29 /*
30 30 * @constructor
31 31 *
32 32 * @param {object|undefined} [options]
33 33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
34 34 */
35 35 var Cell = function (options) {
36 36
37 37 options = this.mergeopt(Cell, options);
38 38 // superclass default overwrite our default
39 39
40 40 this.placeholder = options.placeholder || '';
41 41 this.read_only = options.cm_config.readOnly;
42 42 this.selected = false;
43 43 this.rendered = false;
44 44 this.mode = 'command';
45 45 this.metadata = {};
46 46 // load this from metadata later ?
47 47 this.user_highlight = 'auto';
48 48 this.cm_config = options.cm_config;
49 49 this.cell_id = utils.uuid();
50 50 this._options = options;
51 51
52 52 // For JS VM engines optimization, attributes should be all set (even
53 53 // to null) in the constructor, and if possible, if different subclass
54 54 // have new attributes with same name, they should be created in the
55 55 // same order. Easiest is to create and set to null in parent class.
56 56
57 57 this.element = null;
58 58 this.cell_type = this.cell_type || null;
59 59 this.code_mirror = null;
60 60
61 61 this.create_element();
62 62 if (this.element !== null) {
63 63 this.element.data("cell", this);
64 64 this.bind_events();
65 65 this.init_classes();
66 66 }
67 67 };
68 68
69 69 Cell.options_default = {
70 70 cm_config : {
71 71 indentUnit : 4,
72 72 readOnly: false,
73 73 theme: "default"
74 74 }
75 75 };
76 76
77 77 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
78 78 // by disabling drag/drop altogether on Safari
79 79 // https://github.com/marijnh/CodeMirror/issues/332
80 80 if (utils.browser[0] == "Safari") {
81 81 Cell.options_default.cm_config.dragDrop = false;
82 82 }
83 83
84 84 Cell.prototype.mergeopt = function(_class, options, overwrite){
85 85 options = options || {};
86 86 overwrite = overwrite || {};
87 87 return $.extend(true, {}, _class.options_default, options, overwrite);
88 88 };
89 89
90 90 /**
91 91 * Empty. Subclasses must implement create_element.
92 92 * This should contain all the code to create the DOM element in notebook
93 93 * and will be called by Base Class constructor.
94 94 * @method create_element
95 95 */
96 96 Cell.prototype.create_element = function () {
97 97 };
98 98
99 99 Cell.prototype.init_classes = function () {
100 100 // Call after this.element exists to initialize the css classes
101 101 // related to selected, rendered and mode.
102 102 if (this.selected) {
103 103 this.element.addClass('selected');
104 104 } else {
105 105 this.element.addClass('unselected');
106 106 }
107 107 if (this.rendered) {
108 108 this.element.addClass('rendered');
109 109 } else {
110 110 this.element.addClass('unrendered');
111 111 }
112 112 if (this.mode === 'edit') {
113 113 this.element.addClass('edit_mode');
114 114 } else {
115 115 this.element.addClass('command_mode');
116 116 }
117 117 };
118 118
119 119 /**
120 120 * Subclasses can implement override bind_events.
121 121 * Be carefull to call the parent method when overwriting as it fires event.
122 122 * this will be triggerd after create_element in constructor.
123 123 * @method bind_events
124 124 */
125 125 Cell.prototype.bind_events = function () {
126 126 var that = this;
127 127 // We trigger events so that Cell doesn't have to depend on Notebook.
128 128 that.element.click(function (event) {
129 129 if (!that.selected) {
130 130 $([IPython.events]).trigger('select.Cell', {'cell':that});
131 131 }
132 132 });
133 133 that.element.focusin(function (event) {
134 134 if (!that.selected) {
135 135 $([IPython.events]).trigger('select.Cell', {'cell':that});
136 136 }
137 137 });
138 138 if (this.code_mirror) {
139 139 this.code_mirror.on("change", function(cm, change) {
140 140 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
141 141 });
142 142 }
143 143 if (this.code_mirror) {
144 144 this.code_mirror.on('focus', function(cm, change) {
145 145 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
146 146 });
147 147 }
148 148 if (this.code_mirror) {
149 149 this.code_mirror.on('blur', function(cm, change) {
150 150 // Check if this unfocus event is legit.
151 151 if (!that.should_cancel_blur()) {
152 152 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
153 153 }
154 154 });
155 155 }
156 156 };
157 157
158 158 /**
159 159 * This method gets called in CodeMirror's onKeyDown/onKeyPress
160 160 * handlers and is used to provide custom key handling.
161 161 *
162 162 * To have custom handling, subclasses should override this method, but still call it
163 163 * in order to process the Edit mode keyboard shortcuts.
164 164 *
165 165 * @method handle_codemirror_keyevent
166 166 * @param {CodeMirror} editor - The codemirror instance bound to the cell
167 167 * @param {event} event -
168 168 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
169 169 */
170 170 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
171 171 var that = this;
172 172 var shortcuts = IPython.keyboard_manager.edit_shortcuts;
173 173
174 174 // if this is an edit_shortcuts shortcut, we've already handled it.
175 175 if (shortcuts.use_shortcut(event)) { return true; }
176 176
177 if (event.which === keycodes.esc && event.type === 'keydown') {
178 if (that.code_mirror.options.keyMap === "vim-insert") {
179 // vim keyMap is active and in insert mode. In this case we leave vim
180 // insert mode, but remain in notebook edit mode.
181 // Let' CM handle this event and prevent global handling.
182 event.stop();
183 return false;
184 } else {
185 // vim keyMap is not active. Leave notebook edit mode.
186 // Don't let CM handle the event, defer to global handling.
187 return true;
188 }
189 }
190 177 return false;
191 178 };
192 179
193 180
194 181 /**
195 182 * Triger typsetting of math by mathjax on current cell element
196 183 * @method typeset
197 184 */
198 185 Cell.prototype.typeset = function () {
199 186 if (window.MathJax) {
200 187 var cell_math = this.element.get(0);
201 188 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
202 189 }
203 190 };
204 191
205 192 /**
206 193 * handle cell level logic when a cell is selected
207 194 * @method select
208 195 * @return is the action being taken
209 196 */
210 197 Cell.prototype.select = function () {
211 198 if (!this.selected) {
212 199 this.element.addClass('selected');
213 200 this.element.removeClass('unselected');
214 201 this.selected = true;
215 202 return true;
216 203 } else {
217 204 return false;
218 205 }
219 206 };
220 207
221 208 /**
222 209 * handle cell level logic when a cell is unselected
223 210 * @method unselect
224 211 * @return is the action being taken
225 212 */
226 213 Cell.prototype.unselect = function () {
227 214 if (this.selected) {
228 215 this.element.addClass('unselected');
229 216 this.element.removeClass('selected');
230 217 this.selected = false;
231 218 return true;
232 219 } else {
233 220 return false;
234 221 }
235 222 };
236 223
237 224 /**
238 225 * handle cell level logic when a cell is rendered
239 226 * @method render
240 227 * @return is the action being taken
241 228 */
242 229 Cell.prototype.render = function () {
243 230 if (!this.rendered) {
244 231 this.element.addClass('rendered');
245 232 this.element.removeClass('unrendered');
246 233 this.rendered = true;
247 234 return true;
248 235 } else {
249 236 return false;
250 237 }
251 238 };
252 239
253 240 /**
254 241 * handle cell level logic when a cell is unrendered
255 242 * @method unrender
256 243 * @return is the action being taken
257 244 */
258 245 Cell.prototype.unrender = function () {
259 246 if (this.rendered) {
260 247 this.element.addClass('unrendered');
261 248 this.element.removeClass('rendered');
262 249 this.rendered = false;
263 250 return true;
264 251 } else {
265 252 return false;
266 253 }
267 254 };
268 255
269 256 /**
270 257 * Either delegates keyboard shortcut handling to either IPython keyboard
271 258 * manager when in command mode, or CodeMirror when in edit mode
272 259 *
273 260 * @method handle_keyevent
274 261 * @param {CodeMirror} editor - The codemirror instance bound to the cell
275 262 * @param {event} event -
276 263 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
277 264 */
278 265 Cell.prototype.handle_keyevent = function (editor, event) {
279 266
280 267 // console.log('CM', this.mode, event.which, event.type)
281 268
282 269 if (this.mode === 'command') {
283 270 return true;
284 271 } else if (this.mode === 'edit') {
285 272 return this.handle_codemirror_keyevent(editor, event);
286 273 }
287 274 };
288 275
289 276 /**
290 277 * @method at_top
291 278 * @return {Boolean}
292 279 */
293 280 Cell.prototype.at_top = function () {
294 281 var cm = this.code_mirror
295 282 var cursor = cm.getCursor();
296 283 if (cursor.line === 0 && cm.findPosV(cursor, -1, 'line').hitSide) {
297 284 return true;
298 285 } else {
299 286 return false;
300 287 }
301 288 };
302 289
303 290 /**
304 291 * @method at_bottom
305 292 * @return {Boolean}
306 293 * */
307 294 Cell.prototype.at_bottom = function () {
308 295 var cm = this.code_mirror
309 296 var cursor = cm.getCursor();
310 297 if (cursor.line === (cm.lineCount()-1) && cm.findPosV(cursor, 1, 'line').hitSide) {
311 298 return true;
312 299 } else {
313 300 return false;
314 301 }
315 302 };
316 303 /**
317 304 * enter the command mode for the cell
318 305 * @method command_mode
319 306 * @return is the action being taken
320 307 */
321 308 Cell.prototype.command_mode = function () {
322 309 if (this.mode !== 'command') {
323 310 this.element.addClass('command_mode');
324 311 this.element.removeClass('edit_mode');
325 312 this.mode = 'command';
326 313 return true;
327 314 } else {
328 315 return false;
329 316 }
330 317 };
331 318
332 319 /**
333 320 * enter the edit mode for the cell
334 321 * @method command_mode
335 322 * @return is the action being taken
336 323 */
337 324 Cell.prototype.edit_mode = function () {
338 325 if (this.mode !== 'edit') {
339 326 this.element.addClass('edit_mode');
340 327 this.element.removeClass('command_mode');
341 328 this.mode = 'edit';
342 329 return true;
343 330 } else {
344 331 return false;
345 332 }
346 333 };
347 334
348 335 /**
349 336 * Determine whether or not the unfocus event should be aknowledged.
350 337 *
351 338 * @method should_cancel_blur
352 339 *
353 340 * @return results {bool} Whether or not to ignore the cell's blur event.
354 341 **/
355 342 Cell.prototype.should_cancel_blur = function () {
356 343 return false;
357 344 };
358 345
359 346 /**
360 347 * Focus the cell in the DOM sense
361 348 * @method focus_cell
362 349 */
363 350 Cell.prototype.focus_cell = function () {
364 351 this.element.focus();
365 352 };
366 353
367 354 /**
368 355 * Focus the editor area so a user can type
369 356 *
370 357 * NOTE: If codemirror is focused via a mouse click event, you don't want to
371 358 * call this because it will cause a page jump.
372 359 * @method focus_editor
373 360 */
374 361 Cell.prototype.focus_editor = function () {
375 362 this.refresh();
376 363 this.code_mirror.focus();
377 364 };
378 365
379 366 /**
380 367 * Refresh codemirror instance
381 368 * @method refresh
382 369 */
383 370 Cell.prototype.refresh = function () {
384 371 this.code_mirror.refresh();
385 372 };
386 373
387 374 /**
388 375 * should be overritten by subclass
389 376 * @method get_text
390 377 */
391 378 Cell.prototype.get_text = function () {
392 379 };
393 380
394 381 /**
395 382 * should be overritten by subclass
396 383 * @method set_text
397 384 * @param {string} text
398 385 */
399 386 Cell.prototype.set_text = function (text) {
400 387 };
401 388
402 389 /**
403 390 * should be overritten by subclass
404 391 * serialise cell to json.
405 392 * @method toJSON
406 393 **/
407 394 Cell.prototype.toJSON = function () {
408 395 var data = {};
409 396 data.metadata = this.metadata;
410 397 data.cell_type = this.cell_type;
411 398 return data;
412 399 };
413 400
414 401
415 402 /**
416 403 * should be overritten by subclass
417 404 * @method fromJSON
418 405 **/
419 406 Cell.prototype.fromJSON = function (data) {
420 407 if (data.metadata !== undefined) {
421 408 this.metadata = data.metadata;
422 409 }
423 410 this.celltoolbar.rebuild();
424 411 };
425 412
426 413
427 414 /**
428 415 * can the cell be split into two cells
429 416 * @method is_splittable
430 417 **/
431 418 Cell.prototype.is_splittable = function () {
432 419 return true;
433 420 };
434 421
435 422
436 423 /**
437 424 * can the cell be merged with other cells
438 425 * @method is_mergeable
439 426 **/
440 427 Cell.prototype.is_mergeable = function () {
441 428 return true;
442 429 };
443 430
444 431
445 432 /**
446 433 * @return {String} - the text before the cursor
447 434 * @method get_pre_cursor
448 435 **/
449 436 Cell.prototype.get_pre_cursor = function () {
450 437 var cursor = this.code_mirror.getCursor();
451 438 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
452 439 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 440 return text;
454 441 };
455 442
456 443
457 444 /**
458 445 * @return {String} - the text after the cursor
459 446 * @method get_post_cursor
460 447 **/
461 448 Cell.prototype.get_post_cursor = function () {
462 449 var cursor = this.code_mirror.getCursor();
463 450 var last_line_num = this.code_mirror.lineCount()-1;
464 451 var last_line_len = this.code_mirror.getLine(last_line_num).length;
465 452 var end = {line:last_line_num, ch:last_line_len};
466 453 var text = this.code_mirror.getRange(cursor, end);
467 454 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
468 455 return text;
469 456 };
470 457
471 458 /**
472 459 * Show/Hide CodeMirror LineNumber
473 460 * @method show_line_numbers
474 461 *
475 462 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
476 463 **/
477 464 Cell.prototype.show_line_numbers = function (value) {
478 465 this.code_mirror.setOption('lineNumbers', value);
479 466 this.code_mirror.refresh();
480 467 };
481 468
482 469 /**
483 470 * Toggle CodeMirror LineNumber
484 471 * @method toggle_line_numbers
485 472 **/
486 473 Cell.prototype.toggle_line_numbers = function () {
487 474 var val = this.code_mirror.getOption('lineNumbers');
488 475 this.show_line_numbers(!val);
489 476 };
490 477
491 478 /**
492 479 * Force codemirror highlight mode
493 480 * @method force_highlight
494 481 * @param {object} - CodeMirror mode
495 482 **/
496 483 Cell.prototype.force_highlight = function(mode) {
497 484 this.user_highlight = mode;
498 485 this.auto_highlight();
499 486 };
500 487
501 488 /**
502 489 * Try to autodetect cell highlight mode, or use selected mode
503 490 * @methods _auto_highlight
504 491 * @private
505 492 * @param {String|object|undefined} - CodeMirror mode | 'auto'
506 493 **/
507 494 Cell.prototype._auto_highlight = function (modes) {
508 495 //Here we handle manually selected modes
509 496 var mode;
510 497 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
511 498 {
512 499 mode = this.user_highlight;
513 500 CodeMirror.autoLoadMode(this.code_mirror, mode);
514 501 this.code_mirror.setOption('mode', mode);
515 502 return;
516 503 }
517 504 var current_mode = this.code_mirror.getOption('mode', mode);
518 505 var first_line = this.code_mirror.getLine(0);
519 506 // loop on every pairs
520 507 for(mode in modes) {
521 508 var regs = modes[mode].reg;
522 509 // only one key every time but regexp can't be keys...
523 510 for(var i=0; i<regs.length; i++) {
524 511 // here we handle non magic_modes
525 512 if(first_line.match(regs[i]) !== null) {
526 513 if(current_mode == mode){
527 514 return;
528 515 }
529 516 if (mode.search('magic_') !== 0) {
530 517 this.code_mirror.setOption('mode', mode);
531 518 CodeMirror.autoLoadMode(this.code_mirror, mode);
532 519 return;
533 520 }
534 521 var open = modes[mode].open || "%%";
535 522 var close = modes[mode].close || "%%end";
536 523 var mmode = mode;
537 524 mode = mmode.substr(6);
538 525 if(current_mode == mode){
539 526 return;
540 527 }
541 528 CodeMirror.autoLoadMode(this.code_mirror, mode);
542 529 // create on the fly a mode that swhitch between
543 530 // plain/text and smth else otherwise `%%` is
544 531 // source of some highlight issues.
545 532 // we use patchedGetMode to circumvent a bug in CM
546 533 CodeMirror.defineMode(mmode , function(config) {
547 534 return CodeMirror.multiplexingMode(
548 535 CodeMirror.patchedGetMode(config, 'text/plain'),
549 536 // always set someting on close
550 537 {open: open, close: close,
551 538 mode: CodeMirror.patchedGetMode(config, mode),
552 539 delimStyle: "delimit"
553 540 }
554 541 );
555 542 });
556 543 this.code_mirror.setOption('mode', mmode);
557 544 return;
558 545 }
559 546 }
560 547 }
561 548 // fallback on default
562 549 var default_mode;
563 550 try {
564 551 default_mode = this._options.cm_config.mode;
565 552 } catch(e) {
566 553 default_mode = 'text/plain';
567 554 }
568 555 if( current_mode === default_mode){
569 556 return;
570 557 }
571 558 this.code_mirror.setOption('mode', default_mode);
572 559 };
573 560
574 561 IPython.Cell = Cell;
575 562
576 563 return IPython;
577 564
578 565 }(IPython));
579 566
General Comments 0
You need to be logged in to leave comments. Login now