##// END OF EJS Templates
Special handling for CM's vim keyboard mapping.
Brian E. Granger -
Show More
@@ -1,508 +1,533 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11 /**
12 12 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule CodeCell
16 16 */
17 17
18 18
19 19 /* local util for codemirror */
20 20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21 21
22 22 /**
23 23 *
24 24 * function to delete until previous non blanking space character
25 25 * or first multiple of 4 tabstop.
26 26 * @private
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 35 var select = cm.getRange(from,cur);
36 36 if( select.match(/^\ +$/) !== null){
37 37 cm.replaceRange("",from,cur);
38 38 } else {
39 39 cm.deleteH(-1,"char");
40 40 }
41 41 };
42 42
43 43
44 44 var IPython = (function (IPython) {
45 45 "use strict";
46 46
47 47 var utils = IPython.utils;
48 48 var key = IPython.utils.keycodes;
49 49
50 50 /**
51 51 * A Cell conceived to write code.
52 52 *
53 53 * The kernel doesn't have to be set at creation time, in that case
54 54 * it will be null and set_kernel has to be called later.
55 55 * @class CodeCell
56 56 * @extends IPython.Cell
57 57 *
58 58 * @constructor
59 59 * @param {Object|null} kernel
60 60 * @param {object|undefined} [options]
61 61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 62 */
63 63 var CodeCell = function (kernel, options) {
64 64 this.kernel = kernel || null;
65 65 this.collapsed = false;
66 66
67 67 // create all attributed in constructor function
68 68 // even if null for V8 VM optimisation
69 69 this.input_prompt_number = null;
70 70 this.celltoolbar = null;
71 71 this.output_area = null;
72 72 this.last_msg_id = null;
73 73 this.completer = null;
74 74
75 75
76 76 var cm_overwrite_options = {
77 77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 78 };
79 79
80 80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81 81
82 82 IPython.Cell.apply(this,[options]);
83 83
84 84 // Attributes we want to override in this subclass.
85 85 this.cell_type = "code";
86 86
87 87 var that = this;
88 88 this.element.focusout(
89 89 function() { that.auto_highlight(); }
90 90 );
91 91 };
92 92
93 93 CodeCell.options_default = {
94 94 cm_config : {
95 95 extraKeys: {
96 96 "Tab" : "indentMore",
97 97 "Shift-Tab" : "indentLess",
98 98 "Backspace" : "delSpaceToPrevTabStop",
99 99 "Cmd-/" : "toggleComment",
100 100 "Ctrl-/" : "toggleComment"
101 101 },
102 102 mode: 'ipython',
103 103 theme: 'ipython',
104 104 matchBrackets: true
105 105 }
106 106 };
107 107
108 108
109 109 CodeCell.prototype = new IPython.Cell();
110 110
111 111 /**
112 112 * @method auto_highlight
113 113 */
114 114 CodeCell.prototype.auto_highlight = function () {
115 115 this._auto_highlight(IPython.config.cell_magic_highlight);
116 116 };
117 117
118 118 /** @method create_element */
119 119 CodeCell.prototype.create_element = function () {
120 120 IPython.Cell.prototype.create_element.apply(this, arguments);
121 121
122 122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 123 cell.attr('tabindex','2');
124 124
125 125 var input = $('<div></div>').addClass('input');
126 126 var prompt = $('<div/>').addClass('prompt input_prompt');
127 127 var inner_cell = $('<div/>').addClass('inner_cell');
128 128 this.celltoolbar = new IPython.CellToolbar(this);
129 129 inner_cell.append(this.celltoolbar.element);
130 130 var input_area = $('<div/>').addClass('input_area');
131 131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 133 inner_cell.append(input_area);
134 134 input.append(prompt).append(inner_cell);
135 135 var output = $('<div></div>');
136 136 cell.append(input).append(output);
137 137 this.element = cell;
138 138 this.output_area = new IPython.OutputArea(output, true);
139 139 this.completer = new IPython.Completer(this);
140 140 };
141 141
142 142 /** @method bind_events */
143 143 CodeCell.prototype.bind_events = function () {
144 144 IPython.Cell.prototype.bind_events.apply(this);
145 145 var that = this;
146 146
147 147 this.element.focusout(
148 148 function() { that.auto_highlight(); }
149 149 );
150 150 };
151 151
152 152 CodeCell.prototype.handle_keyevent = function (editor, event) {
153 153
154 154 console.log('CM', this.mode, event.which, event.type)
155 155
156 156 if (this.mode === 'command') {
157 157 return true;
158 158 } else if (this.mode === 'edit') {
159 159 return this.handle_codemirror_keyevent(editor, event);
160 160 }
161 161 };
162 162
163 163 /**
164 164 * This method gets called in CodeMirror's onKeyDown/onKeyPress
165 165 * handlers and is used to provide custom key handling. Its return
166 166 * value is used to determine if CodeMirror should ignore the event:
167 167 * true = ignore, false = don't ignore.
168 168 * @method handle_codemirror_keyevent
169 169 */
170 170 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
171 171
172 172 var that = this;
173 173 // whatever key is pressed, first, cancel the tooltip request before
174 174 // they are sent, and remove tooltip if any, except for tab again
175 var tooltip_closed = null;
175 176 if (event.type === 'keydown' && event.which != key.TAB ) {
176 IPython.tooltip.remove_and_cancel_tooltip();
177 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
177 178 }
178 179
179 180 var cur = editor.getCursor();
180 181 if (event.keyCode === key.ENTER){
181 182 this.auto_highlight();
182 183 }
183 184
184 185 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
185 186 // Always ignore shift-enter in CodeMirror as we handle it.
186 187 return true;
187 188 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
188 189 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
189 190 // browser and keyboard layout !
190 191 // Pressing '(' , request tooltip, don't forget to reappend it
191 192 // The second argument says to hide the tooltip if the docstring
192 193 // is actually empty
193 194 IPython.tooltip.pending(that, true);
194 195 } else if (event.which === key.UPARROW && event.type === 'keydown') {
195 196 // If we are not at the top, let CM handle the up arrow and
196 197 // prevent the global keydown handler from handling it.
197 198 if (!that.at_top()) {
198 199 event.stop();
199 200 return false;
200 201 } else {
201 202 return true;
202 203 }
203 } else if (event.which === key.ESC) {
204 return IPython.tooltip.remove_and_cancel_tooltip(true);
204 } else if (event.which === key.ESC && event.type === 'keydown') {
205 // First see if the tooltip is active and if so cancel it.
206 if (tooltip_closed) {
207 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
208 // force=true. Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
213 }
214 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
216 event.stop();
217 return true;
218 }
219 if (that.code_mirror.options.keyMap === "vim-insert") {
220 // vim keyMap is active and in insert mode. In this case we leave vim
221 // insert mode, but remain in notebook edit mode.
222 // Let' CM handle this event and prevent global handling.
223 event.stop();
224 return false;
225 } else {
226 // vim keyMap is not active. Leave notebook edit mode.
227 // Don't let CM handle the event, defer to global handling.
228 return true;
229 }
205 230 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
206 231 // If we are not at the bottom, let CM handle the down arrow and
207 232 // prevent the global keydown handler from handling it.
208 233 if (!that.at_bottom()) {
209 234 event.stop();
210 235 return false;
211 236 } else {
212 237 return true;
213 238 }
214 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
239 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
215 240 if (editor.somethingSelected()){
216 241 var anchor = editor.getCursor("anchor");
217 242 var head = editor.getCursor("head");
218 243 if( anchor.line != head.line){
219 244 return false;
220 245 }
221 246 }
222 247 IPython.tooltip.request(that);
223 248 event.stop();
224 249 return true;
225 250 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
226 251 // Tab completion.
227 252 IPython.tooltip.remove_and_cancel_tooltip();
228 253 if (editor.somethingSelected()) {
229 254 return false;
230 255 }
231 256 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
232 257 if (pre_cursor.trim() === "") {
233 258 // Don't autocomplete if the part of the line before the cursor
234 259 // is empty. In this case, let CodeMirror handle indentation.
235 260 return false;
236 261 } else {
237 262 event.stop();
238 263 this.completer.startCompletion();
239 264 return true;
240 265 }
241 266 } else {
242 267 // keypress/keyup also trigger on TAB press, and we don't want to
243 268 // use those to disable tab completion.
244 269 return false;
245 270 }
246 271 return false;
247 272 };
248 273
249 274 // Kernel related calls.
250 275
251 276 CodeCell.prototype.set_kernel = function (kernel) {
252 277 this.kernel = kernel;
253 278 };
254 279
255 280 /**
256 281 * Execute current code cell to the kernel
257 282 * @method execute
258 283 */
259 284 CodeCell.prototype.execute = function () {
260 285 this.output_area.clear_output();
261 286 this.set_input_prompt('*');
262 287 this.element.addClass("running");
263 288 if (this.last_msg_id) {
264 289 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
265 290 }
266 291 var callbacks = this.get_callbacks();
267 292
268 293 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
269 294 };
270 295
271 296 /**
272 297 * Construct the default callbacks for
273 298 * @method get_callbacks
274 299 */
275 300 CodeCell.prototype.get_callbacks = function () {
276 301 return {
277 302 shell : {
278 303 reply : $.proxy(this._handle_execute_reply, this),
279 304 payload : {
280 305 set_next_input : $.proxy(this._handle_set_next_input, this),
281 306 page : $.proxy(this._open_with_pager, this)
282 307 }
283 308 },
284 309 iopub : {
285 310 output : $.proxy(this.output_area.handle_output, this.output_area),
286 311 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
287 312 },
288 313 input : $.proxy(this._handle_input_request, this)
289 314 };
290 315 };
291 316
292 317 CodeCell.prototype._open_with_pager = function (payload) {
293 318 $([IPython.events]).trigger('open_with_text.Pager', payload);
294 319 };
295 320
296 321 /**
297 322 * @method _handle_execute_reply
298 323 * @private
299 324 */
300 325 CodeCell.prototype._handle_execute_reply = function (msg) {
301 326 this.set_input_prompt(msg.content.execution_count);
302 327 this.element.removeClass("running");
303 328 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
304 329 };
305 330
306 331 /**
307 332 * @method _handle_set_next_input
308 333 * @private
309 334 */
310 335 CodeCell.prototype._handle_set_next_input = function (payload) {
311 336 var data = {'cell': this, 'text': payload.text};
312 337 $([IPython.events]).trigger('set_next_input.Notebook', data);
313 338 };
314 339
315 340 /**
316 341 * @method _handle_input_request
317 342 * @private
318 343 */
319 344 CodeCell.prototype._handle_input_request = function (msg) {
320 345 this.output_area.append_raw_input(msg);
321 346 };
322 347
323 348
324 349 // Basic cell manipulation.
325 350
326 351 CodeCell.prototype.select = function () {
327 352 var cont = IPython.Cell.prototype.select.apply(this);
328 353 if (cont) {
329 354 this.code_mirror.refresh();
330 355 this.auto_highlight();
331 356 };
332 357 return cont;
333 358 };
334 359
335 360 CodeCell.prototype.render = function () {
336 361 var cont = IPython.Cell.prototype.render.apply(this);
337 362 // Always execute, even if we are already in the rendered state
338 363 return cont;
339 364 };
340 365
341 366 CodeCell.prototype.unrender = function () {
342 367 // CodeCell is always rendered
343 368 return false;
344 369 };
345 370
346 371 CodeCell.prototype.command_mode = function () {
347 372 var cont = IPython.Cell.prototype.command_mode.apply(this);
348 373 if (cont) {
349 374 this.focus_cell();
350 375 };
351 376 return cont;
352 377 }
353 378
354 379 CodeCell.prototype.edit_mode = function () {
355 380 var cont = IPython.Cell.prototype.edit_mode.apply(this);
356 381 if (cont) {
357 382 this.focus_editor();
358 383 };
359 384 return cont;
360 385 }
361 386
362 387 CodeCell.prototype.select_all = function () {
363 388 var start = {line: 0, ch: 0};
364 389 var nlines = this.code_mirror.lineCount();
365 390 var last_line = this.code_mirror.getLine(nlines-1);
366 391 var end = {line: nlines-1, ch: last_line.length};
367 392 this.code_mirror.setSelection(start, end);
368 393 };
369 394
370 395
371 396 CodeCell.prototype.collapse = function () {
372 397 this.collapsed = true;
373 398 this.output_area.collapse();
374 399 };
375 400
376 401
377 402 CodeCell.prototype.expand = function () {
378 403 this.collapsed = false;
379 404 this.output_area.expand();
380 405 };
381 406
382 407
383 408 CodeCell.prototype.toggle_output = function () {
384 409 this.collapsed = Boolean(1 - this.collapsed);
385 410 this.output_area.toggle_output();
386 411 };
387 412
388 413
389 414 CodeCell.prototype.toggle_output_scroll = function () {
390 415 this.output_area.toggle_scroll();
391 416 };
392 417
393 418
394 419 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
395 420 var ns = prompt_value || "&nbsp;";
396 421 return 'In&nbsp;[' + ns + ']:';
397 422 };
398 423
399 424 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
400 425 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
401 426 for(var i=1; i < lines_number; i++) {
402 427 html.push(['...:']);
403 428 }
404 429 return html.join('<br/>');
405 430 };
406 431
407 432 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
408 433
409 434
410 435 CodeCell.prototype.set_input_prompt = function (number) {
411 436 var nline = 1;
412 437 if (this.code_mirror !== undefined) {
413 438 nline = this.code_mirror.lineCount();
414 439 }
415 440 this.input_prompt_number = number;
416 441 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
417 442 this.element.find('div.input_prompt').html(prompt_html);
418 443 };
419 444
420 445
421 446 CodeCell.prototype.clear_input = function () {
422 447 this.code_mirror.setValue('');
423 448 };
424 449
425 450
426 451 CodeCell.prototype.get_text = function () {
427 452 return this.code_mirror.getValue();
428 453 };
429 454
430 455
431 456 CodeCell.prototype.set_text = function (code) {
432 457 return this.code_mirror.setValue(code);
433 458 };
434 459
435 460
436 461 CodeCell.prototype.at_top = function () {
437 462 var cursor = this.code_mirror.getCursor();
438 463 if (cursor.line === 0 && cursor.ch === 0) {
439 464 return true;
440 465 } else {
441 466 return false;
442 467 }
443 468 };
444 469
445 470
446 471 CodeCell.prototype.at_bottom = function () {
447 472 var cursor = this.code_mirror.getCursor();
448 473 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
449 474 return true;
450 475 } else {
451 476 return false;
452 477 }
453 478 };
454 479
455 480
456 481 CodeCell.prototype.clear_output = function (wait) {
457 482 this.output_area.clear_output(wait);
458 483 };
459 484
460 485
461 486 // JSON serialization
462 487
463 488 CodeCell.prototype.fromJSON = function (data) {
464 489 IPython.Cell.prototype.fromJSON.apply(this, arguments);
465 490 if (data.cell_type === 'code') {
466 491 if (data.input !== undefined) {
467 492 this.set_text(data.input);
468 493 // make this value the starting point, so that we can only undo
469 494 // to this state, instead of a blank cell
470 495 this.code_mirror.clearHistory();
471 496 this.auto_highlight();
472 497 }
473 498 if (data.prompt_number !== undefined) {
474 499 this.set_input_prompt(data.prompt_number);
475 500 } else {
476 501 this.set_input_prompt();
477 502 }
478 503 this.output_area.fromJSON(data.outputs);
479 504 if (data.collapsed !== undefined) {
480 505 if (data.collapsed) {
481 506 this.collapse();
482 507 } else {
483 508 this.expand();
484 509 }
485 510 }
486 511 }
487 512 };
488 513
489 514
490 515 CodeCell.prototype.toJSON = function () {
491 516 var data = IPython.Cell.prototype.toJSON.apply(this);
492 517 data.input = this.get_text();
493 518 // is finite protect against undefined and '*' value
494 519 if (isFinite(this.input_prompt_number)) {
495 520 data.prompt_number = this.input_prompt_number;
496 521 }
497 522 var outputs = this.output_area.toJSON();
498 523 data.outputs = outputs;
499 524 data.language = 'python';
500 525 data.collapsed = this.collapsed;
501 526 return data;
502 527 };
503 528
504 529
505 530 IPython.CodeCell = CodeCell;
506 531
507 532 return IPython;
508 533 }(IPython));
@@ -1,589 +1,601 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // TextCell
10 10 //============================================================================
11 11
12 12
13 13
14 14 /**
15 15 A module that allow to create different type of Text Cell
16 16 @module IPython
17 17 @namespace IPython
18 18 */
19 19 var IPython = (function (IPython) {
20 20 "use strict";
21 21
22 22 // TextCell base class
23 23 var key = IPython.utils.keycodes;
24 24
25 25 /**
26 26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 27 * cell start as not redered.
28 28 *
29 29 * @class TextCell
30 30 * @constructor TextCell
31 31 * @extend IPython.Cell
32 32 * @param {object|undefined} [options]
33 33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 35 */
36 36 var TextCell = function (options) {
37 37 // in all TextCell/Cell subclasses
38 38 // do not assign most of members here, just pass it down
39 39 // in the options dict potentially overwriting what you wish.
40 40 // they will be assigned in the base class.
41 41
42 42 // we cannot put this as a class key as it has handle to "this".
43 43 var cm_overwrite_options = {
44 44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 45 };
46 46
47 47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48 48
49 49 this.cell_type = this.cell_type || 'text';
50 50
51 51 IPython.Cell.apply(this, [options]);
52 52
53 53 this.rendered = false;
54 54 };
55 55
56 56 TextCell.prototype = new IPython.Cell();
57 57
58 58 TextCell.options_default = {
59 59 cm_config : {
60 60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 61 mode: 'htmlmixed',
62 62 lineWrapping : true,
63 63 }
64 64 };
65 65
66 66
67 67 /**
68 68 * Create the DOM element of the TextCell
69 69 * @method create_element
70 70 * @private
71 71 */
72 72 TextCell.prototype.create_element = function () {
73 73 IPython.Cell.prototype.create_element.apply(this, arguments);
74 74
75 75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 76 cell.attr('tabindex','2');
77 77
78 78 var prompt = $('<div/>').addClass('prompt input_prompt');
79 79 cell.append(prompt);
80 80 var inner_cell = $('<div/>').addClass('inner_cell');
81 81 this.celltoolbar = new IPython.CellToolbar(this);
82 82 inner_cell.append(this.celltoolbar.element);
83 83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
84 84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
85 85 // The tabindex=-1 makes this div focusable.
86 86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 87 addClass('rendered_html').attr('tabindex','-1');
88 88 inner_cell.append(input_area).append(render_area);
89 89 cell.append(inner_cell);
90 90 this.element = cell;
91 91 };
92 92
93 93
94 94 /**
95 95 * Bind the DOM evet to cell actions
96 96 * Need to be called after TextCell.create_element
97 97 * @private
98 98 * @method bind_event
99 99 */
100 100 TextCell.prototype.bind_events = function () {
101 101 IPython.Cell.prototype.bind_events.apply(this);
102 102 var that = this;
103 103
104 104 this.element.dblclick(function () {
105 105 if (that.selected === false) {
106 106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 107 };
108 108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
109 109 });
110 110 };
111 111
112 112 TextCell.prototype.handle_keyevent = function (editor, event) {
113 113
114 114 console.log('CM', this.mode, event.which, event.type)
115 115
116 116 if (this.mode === 'command') {
117 117 return true;
118 118 } else if (this.mode === 'edit') {
119 119 return this.handle_codemirror_keyevent(editor, event);
120 120 }
121 121 };
122 122
123 123 /**
124 124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
125 125 * handlers and is used to provide custom key handling.
126 126 *
127 127 * Subclass should override this method to have custom handeling
128 128 *
129 129 * @method handle_codemirror_keyevent
130 130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
131 131 * @param {event} event -
132 132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
133 133 */
134 134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
135 135 var that = this;
136 136
137 137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
138 138 // Always ignore shift-enter in CodeMirror as we handle it.
139 139 return true;
140 140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
141 141 // If we are not at the top, let CM handle the up arrow and
142 142 // prevent the global keydown handler from handling it.
143 143 if (!that.at_top()) {
144 144 event.stop();
145 145 return false;
146 146 } else {
147 147 return true;
148 148 };
149 149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
150 150 // If we are not at the bottom, let CM handle the down arrow and
151 151 // prevent the global keydown handler from handling it.
152 152 if (!that.at_bottom()) {
153 153 event.stop();
154 154 return false;
155 155 } else {
156 156 return true;
157 157 };
158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 // vim keyMap is active and in insert mode. In this case we leave vim
161 // insert mode, but remain in notebook edit mode.
162 // Let' CM handle this event and prevent global handling.
163 event.stop();
164 return false;
165 } else {
166 // vim keyMap is not active. Leave notebook edit mode.
167 // Don't let CM handle the event, defer to global handling.
168 return true;
169 }
158 170 }
159 171 return false;
160 172 };
161 173
162 174 // Cell level actions
163 175
164 176 TextCell.prototype.select = function () {
165 177 var cont = IPython.Cell.prototype.select.apply(this);
166 178 if (cont) {
167 179 if (this.mode === 'edit') {
168 180 this.code_mirror.refresh();
169 181 }
170 182 };
171 183 return cont;
172 184 };
173 185
174 186 TextCell.prototype.unrender = function () {
175 187 if (this.read_only) return;
176 188 var cont = IPython.Cell.prototype.unrender.apply(this);
177 189 if (cont) {
178 190 var text_cell = this.element;
179 191 var output = text_cell.find("div.text_cell_render");
180 192 output.hide();
181 193 text_cell.find('div.text_cell_input').show();
182 194 if (this.get_text() === this.placeholder) {
183 195 this.set_text('');
184 196 this.refresh();
185 197 }
186 198
187 199 };
188 200 return cont;
189 201 };
190 202
191 203 TextCell.prototype.execute = function () {
192 204 this.render();
193 205 };
194 206
195 207 TextCell.prototype.command_mode = function () {
196 208 var cont = IPython.Cell.prototype.command_mode.apply(this);
197 209 if (cont) {
198 210 this.focus_cell();
199 211 };
200 212 return cont;
201 213 }
202 214
203 215 TextCell.prototype.edit_mode = function () {
204 216 var cont = IPython.Cell.prototype.edit_mode.apply(this);
205 217 if (cont) {
206 218 this.unrender();
207 219 this.focus_editor();
208 220 };
209 221 return cont;
210 222 }
211 223
212 224 /**
213 225 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
214 226 * @method get_text
215 227 * @retrun {string} CodeMirror current text value
216 228 */
217 229 TextCell.prototype.get_text = function() {
218 230 return this.code_mirror.getValue();
219 231 };
220 232
221 233 /**
222 234 * @param {string} text - Codemiror text value
223 235 * @see TextCell#get_text
224 236 * @method set_text
225 237 * */
226 238 TextCell.prototype.set_text = function(text) {
227 239 this.code_mirror.setValue(text);
228 240 this.code_mirror.refresh();
229 241 };
230 242
231 243 /**
232 244 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
233 245 * @method get_rendered
234 246 * @return {html} html of rendered element
235 247 * */
236 248 TextCell.prototype.get_rendered = function() {
237 249 return this.element.find('div.text_cell_render').html();
238 250 };
239 251
240 252 /**
241 253 * @method set_rendered
242 254 */
243 255 TextCell.prototype.set_rendered = function(text) {
244 256 this.element.find('div.text_cell_render').html(text);
245 257 };
246 258
247 259 /**
248 260 * @method at_top
249 261 * @return {Boolean}
250 262 */
251 263 TextCell.prototype.at_top = function () {
252 264 if (this.rendered) {
253 265 return true;
254 266 } else {
255 267 var cursor = this.code_mirror.getCursor();
256 268 if (cursor.line === 0 && cursor.ch === 0) {
257 269 return true;
258 270 } else {
259 271 return false;
260 272 };
261 273 };
262 274 };
263 275
264 276 /**
265 277 * @method at_bottom
266 278 * @return {Boolean}
267 279 * */
268 280 TextCell.prototype.at_bottom = function () {
269 281 if (this.rendered) {
270 282 return true;
271 283 } else {
272 284 var cursor = this.code_mirror.getCursor();
273 285 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
274 286 return true;
275 287 } else {
276 288 return false;
277 289 };
278 290 };
279 291 };
280 292
281 293 /**
282 294 * Create Text cell from JSON
283 295 * @param {json} data - JSON serialized text-cell
284 296 * @method fromJSON
285 297 */
286 298 TextCell.prototype.fromJSON = function (data) {
287 299 IPython.Cell.prototype.fromJSON.apply(this, arguments);
288 300 if (data.cell_type === this.cell_type) {
289 301 if (data.source !== undefined) {
290 302 this.set_text(data.source);
291 303 // make this value the starting point, so that we can only undo
292 304 // to this state, instead of a blank cell
293 305 this.code_mirror.clearHistory();
294 306 this.set_rendered(data.rendered || '');
295 307 this.rendered = false;
296 308 this.render();
297 309 }
298 310 }
299 311 };
300 312
301 313 /** Generate JSON from cell
302 314 * @return {object} cell data serialised to json
303 315 */
304 316 TextCell.prototype.toJSON = function () {
305 317 var data = IPython.Cell.prototype.toJSON.apply(this);
306 318 data.source = this.get_text();
307 319 if (data.source == this.placeholder) {
308 320 data.source = "";
309 321 }
310 322 return data;
311 323 };
312 324
313 325
314 326 /**
315 327 * @class MarkdownCell
316 328 * @constructor MarkdownCell
317 329 * @extends IPython.HTMLCell
318 330 */
319 331 var MarkdownCell = function (options) {
320 332 options = this.mergeopt(MarkdownCell, options);
321 333
322 334 this.cell_type = 'markdown';
323 335 TextCell.apply(this, [options]);
324 336 };
325 337
326 338 MarkdownCell.options_default = {
327 339 cm_config: {
328 340 mode: 'gfm'
329 341 },
330 342 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
331 343 }
332 344
333 345 MarkdownCell.prototype = new TextCell();
334 346
335 347 /**
336 348 * @method render
337 349 */
338 350 MarkdownCell.prototype.render = function () {
339 351 var cont = IPython.TextCell.prototype.render.apply(this);
340 352 if (cont) {
341 353 var text = this.get_text();
342 354 var math = null;
343 355 if (text === "") { text = this.placeholder; }
344 356 var text_and_math = IPython.mathjaxutils.remove_math(text);
345 357 text = text_and_math[0];
346 358 math = text_and_math[1];
347 359 var html = marked.parser(marked.lexer(text));
348 360 html = $(IPython.mathjaxutils.replace_math(html, math));
349 361 // links in markdown cells should open in new tabs
350 362 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
351 363 try {
352 364 this.set_rendered(html);
353 365 } catch (e) {
354 366 console.log("Error running Javascript in Markdown:");
355 367 console.log(e);
356 368 this.set_rendered($("<div/>").addClass("js-error").html(
357 369 "Error rendering Markdown!<br/>" + e.toString())
358 370 );
359 371 }
360 372 this.element.find('div.text_cell_input').hide();
361 373 this.element.find("div.text_cell_render").show();
362 374 this.typeset()
363 375 };
364 376 return cont;
365 377 };
366 378
367 379
368 380 // RawCell
369 381
370 382 /**
371 383 * @class RawCell
372 384 * @constructor RawCell
373 385 * @extends IPython.TextCell
374 386 */
375 387 var RawCell = function (options) {
376 388
377 389 options = this.mergeopt(RawCell,options)
378 390 TextCell.apply(this, [options]);
379 391 this.cell_type = 'raw';
380 392 // RawCell should always hide its rendered div
381 393 this.element.find('div.text_cell_render').hide();
382 394 };
383 395
384 396 RawCell.options_default = {
385 397 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
386 398 "It will not be rendered in the notebook.\n" +
387 399 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
388 400 };
389 401
390 402 RawCell.prototype = new TextCell();
391 403
392 404 /** @method bind_events **/
393 405 RawCell.prototype.bind_events = function () {
394 406 TextCell.prototype.bind_events.apply(this);
395 407 var that = this
396 408 this.element.focusout(function() {
397 409 that.auto_highlight();
398 410 });
399 411 };
400 412
401 413 /**
402 414 * Trigger autodetection of highlight scheme for current cell
403 415 * @method auto_highlight
404 416 */
405 417 RawCell.prototype.auto_highlight = function () {
406 418 this._auto_highlight(IPython.config.raw_cell_highlight);
407 419 };
408 420
409 421 /** @method render **/
410 422 RawCell.prototype.render = function () {
411 423 // Make sure that this cell type can never be rendered
412 424 if (this.rendered) {
413 425 this.unrender();
414 426 }
415 427 var text = this.get_text();
416 428 if (text === "") { text = this.placeholder; }
417 429 this.set_text(text);
418 430 };
419 431
420 432
421 433 /** @method handle_codemirror_keyevent **/
422 434 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
423 435
424 436 var that = this;
425 437 if (this.mode === 'command') {
426 438 return false
427 439 } else if (this.mode === 'edit') {
428 440 // TODO: review these handlers...
429 441 if (event.which === key.UPARROW && event.type === 'keydown') {
430 442 // If we are not at the top, let CM handle the up arrow and
431 443 // prevent the global keydown handler from handling it.
432 444 if (!that.at_top()) {
433 445 event.stop();
434 446 return false;
435 447 } else {
436 448 return true;
437 449 };
438 450 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
439 451 // If we are not at the bottom, let CM handle the down arrow and
440 452 // prevent the global keydown handler from handling it.
441 453 if (!that.at_bottom()) {
442 454 event.stop();
443 455 return false;
444 456 } else {
445 457 return true;
446 458 };
447 459 };
448 460 return false;
449 461 };
450 462 return false;
451 463 };
452 464
453 465
454 466 /**
455 467 * @class HeadingCell
456 468 * @extends IPython.TextCell
457 469 */
458 470
459 471 /**
460 472 * @constructor HeadingCell
461 473 * @extends IPython.TextCell
462 474 */
463 475 var HeadingCell = function (options) {
464 476 options = this.mergeopt(HeadingCell, options);
465 477
466 478 this.level = 1;
467 479 this.cell_type = 'heading';
468 480 TextCell.apply(this, [options]);
469 481
470 482 /**
471 483 * heading level of the cell, use getter and setter to access
472 484 * @property level
473 485 */
474 486 };
475 487
476 488 HeadingCell.options_default = {
477 489 placeholder: "Type Heading Here"
478 490 };
479 491
480 492 HeadingCell.prototype = new TextCell();
481 493
482 494 /** @method fromJSON */
483 495 HeadingCell.prototype.fromJSON = function (data) {
484 496 if (data.level != undefined){
485 497 this.level = data.level;
486 498 }
487 499 TextCell.prototype.fromJSON.apply(this, arguments);
488 500 };
489 501
490 502
491 503 /** @method toJSON */
492 504 HeadingCell.prototype.toJSON = function () {
493 505 var data = TextCell.prototype.toJSON.apply(this);
494 506 data.level = this.get_level();
495 507 return data;
496 508 };
497 509
498 510 /**
499 511 * can the cell be split into two cells
500 512 * @method is_splittable
501 513 **/
502 514 HeadingCell.prototype.is_splittable = function () {
503 515 return false;
504 516 };
505 517
506 518
507 519 /**
508 520 * can the cell be merged with other cells
509 521 * @method is_mergeable
510 522 **/
511 523 HeadingCell.prototype.is_mergeable = function () {
512 524 return false;
513 525 };
514 526
515 527 /**
516 528 * Change heading level of cell, and re-render
517 529 * @method set_level
518 530 */
519 531 HeadingCell.prototype.set_level = function (level) {
520 532 this.level = level;
521 533 if (this.rendered) {
522 534 this.rendered = false;
523 535 this.render();
524 536 };
525 537 };
526 538
527 539 /** The depth of header cell, based on html (h1 to h6)
528 540 * @method get_level
529 541 * @return {integer} level - for 1 to 6
530 542 */
531 543 HeadingCell.prototype.get_level = function () {
532 544 return this.level;
533 545 };
534 546
535 547
536 548 HeadingCell.prototype.set_rendered = function (html) {
537 549 this.element.find("div.text_cell_render").html(html);
538 550 };
539 551
540 552
541 553 HeadingCell.prototype.get_rendered = function () {
542 554 var r = this.element.find("div.text_cell_render");
543 555 return r.children().first().html();
544 556 };
545 557
546 558
547 559 HeadingCell.prototype.render = function () {
548 560 var cont = IPython.TextCell.prototype.render.apply(this);
549 561 if (cont) {
550 562 var text = this.get_text();
551 563 var math = null;
552 564 // Markdown headings must be a single line
553 565 text = text.replace(/\n/g, ' ');
554 566 if (text === "") { text = this.placeholder; }
555 567 text = Array(this.level + 1).join("#") + " " + text;
556 568 var text_and_math = IPython.mathjaxutils.remove_math(text);
557 569 text = text_and_math[0];
558 570 math = text_and_math[1];
559 571 var html = marked.parser(marked.lexer(text));
560 572 var h = $(IPython.mathjaxutils.replace_math(html, math));
561 573 // add id and linkback anchor
562 574 var hash = h.text().replace(/ /g, '-');
563 575 h.attr('id', hash);
564 576 h.append(
565 577 $('<a/>')
566 578 .addClass('anchor-link')
567 579 .attr('href', '#' + hash)
568 580 .text('¶')
569 581 );
570 582
571 583 this.set_rendered(h);
572 584 this.typeset();
573 585 this.element.find('div.text_cell_input').hide();
574 586 this.element.find("div.text_cell_render").show();
575 587
576 588 };
577 589 return cont;
578 590 };
579 591
580 592 IPython.TextCell = TextCell;
581 593 IPython.MarkdownCell = MarkdownCell;
582 594 IPython.RawCell = RawCell;
583 595 IPython.HeadingCell = HeadingCell;
584 596
585 597
586 598 return IPython;
587 599
588 600 }(IPython));
589 601
General Comments 0
You need to be logged in to leave comments. Login now