##// END OF EJS Templates
Pop output now removes a specific output
Jonathan Frederic -
Show More
@@ -1,570 +1,573
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 'notebook/js/cell',
18 18 'notebook/js/outputarea',
19 19 'notebook/js/completer',
20 20 'notebook/js/celltoolbar',
21 21 'codemirror/lib/codemirror',
22 22 'codemirror/mode/python/python',
23 23 'notebook/js/codemirror-ipython'
24 24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
25 25 "use strict";
26 26
27 27 var Cell = cell.Cell;
28 28
29 29 /* local util for codemirror */
30 30 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
31 31
32 32 /**
33 33 *
34 34 * function to delete until previous non blanking space character
35 35 * or first multiple of 4 tabstop.
36 36 * @private
37 37 */
38 38 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
39 39 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
40 40 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
41 41 var cur = cm.getCursor(), line = cm.getLine(cur.line);
42 42 var tabsize = cm.getOption('tabSize');
43 43 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
44 44 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
45 45 var select = cm.getRange(from,cur);
46 46 if( select.match(/^\ +$/) !== null){
47 47 cm.replaceRange("",from,cur);
48 48 } else {
49 49 cm.deleteH(-1,"char");
50 50 }
51 51 };
52 52
53 53 var keycodes = keyboard.keycodes;
54 54
55 55 var CodeCell = function (kernel, options) {
56 56 // Constructor
57 57 //
58 58 // A Cell conceived to write code.
59 59 //
60 60 // Parameters:
61 61 // kernel: Kernel instance
62 62 // The kernel doesn't have to be set at creation time, in that case
63 63 // it will be null and set_kernel has to be called later.
64 64 // options: dictionary
65 65 // Dictionary of keyword arguments.
66 66 // events: $(Events) instance
67 67 // config: dictionary
68 68 // keyboard_manager: KeyboardManager instance
69 69 // notebook: Notebook instance
70 70 // tooltip: Tooltip instance
71 71 this.kernel = kernel || null;
72 72 this.notebook = options.notebook;
73 73 this.collapsed = false;
74 74 this.events = options.events;
75 75 this.tooltip = options.tooltip;
76 76 this.config = options.config;
77 77
78 78 // create all attributed in constructor function
79 79 // even if null for V8 VM optimisation
80 80 this.input_prompt_number = null;
81 81 this.celltoolbar = null;
82 82 this.output_area = null;
83 83 // Keep a stack of the 'active' output areas (where active means the
84 84 // output area that recieves output). When a user activates an output
85 85 // area, it gets pushed to the stack. Then, when the output area is
86 86 // deactivated, it's popped from the stack. When the stack is empty,
87 87 // the cell's output area is used.
88 88 this.active_output_area = [];
89 89 this.last_msg_id = null;
90 90 this.completer = null;
91 91
92 92
93 93 var config = utils.mergeopt(CodeCell, this.config);
94 94 Cell.apply(this,[{
95 95 config: config,
96 96 keyboard_manager: options.keyboard_manager,
97 97 events: this.events}]);
98 98
99 99 // Attributes we want to override in this subclass.
100 100 this.cell_type = "code";
101 101
102 102 var that = this;
103 103 this.element.focusout(
104 104 function() { that.auto_highlight(); }
105 105 );
106 106 };
107 107
108 108 CodeCell.options_default = {
109 109 cm_config : {
110 110 extraKeys: {
111 111 "Tab" : "indentMore",
112 112 "Shift-Tab" : "indentLess",
113 113 "Backspace" : "delSpaceToPrevTabStop",
114 114 "Cmd-/" : "toggleComment",
115 115 "Ctrl-/" : "toggleComment"
116 116 },
117 117 mode: 'ipython',
118 118 theme: 'ipython',
119 119 matchBrackets: true
120 120 }
121 121 };
122 122
123 123 CodeCell.msg_cells = {};
124 124
125 125 CodeCell.prototype = Object.create(Cell.prototype);
126 126
127 127 /**
128 128 * @method get_output_area
129 129 */
130 130 CodeCell.prototype.get_output_area = function () {
131 131 if (this.active_output_area && this.active_output_area.length > 0) {
132 132 return this.active_output_area[this.active_output_area.length-1];
133 133 } else {
134 134 return this.output_area;
135 135 }
136 136 };
137 137
138 138 /**
139 139 * @method push_output_area
140 140 */
141 141 CodeCell.prototype.push_output_area = function (output_area) {
142 142 this.active_output_area.push(output_area);
143 143 };
144 144
145 145 /**
146 146 * @method pop_output_area
147 147 */
148 CodeCell.prototype.pop_output_area = function () {
149 this.active_output_area.pop();
148 CodeCell.prototype.pop_output_area = function (output_area) {
149 var index = this.active_output_area.lastIndexOf(output_area);
150 if (index > -1) {
151 this.active_output_area.splice(index, 1);
152 }
150 153 };
151 154
152 155 /**
153 156 * @method auto_highlight
154 157 */
155 158 CodeCell.prototype.auto_highlight = function () {
156 159 this._auto_highlight(this.config.cell_magic_highlight);
157 160 };
158 161
159 162 /** @method create_element */
160 163 CodeCell.prototype.create_element = function () {
161 164 Cell.prototype.create_element.apply(this, arguments);
162 165
163 166 var cell = $('<div></div>').addClass('cell code_cell');
164 167 cell.attr('tabindex','2');
165 168
166 169 var input = $('<div></div>').addClass('input');
167 170 var prompt = $('<div/>').addClass('prompt input_prompt');
168 171 var inner_cell = $('<div/>').addClass('inner_cell');
169 172 this.celltoolbar = new celltoolbar.CellToolbar({
170 173 cell: this,
171 174 notebook: this.notebook});
172 175 inner_cell.append(this.celltoolbar.element);
173 176 var input_area = $('<div/>').addClass('input_area');
174 177 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
175 178 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
176 179 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
177 180 inner_cell.append(input_area);
178 181 input.append(prompt).append(inner_cell);
179 182
180 183 var widget_area = $('<div/>')
181 184 .addClass('widget-area')
182 185 .hide();
183 186 this.widget_area = widget_area;
184 187 var widget_prompt = $('<div/>')
185 188 .addClass('prompt')
186 189 .appendTo(widget_area);
187 190 var widget_subarea = $('<div/>')
188 191 .addClass('widget-subarea')
189 192 .appendTo(widget_area);
190 193 this.widget_subarea = widget_subarea;
191 194 var widget_clear_buton = $('<button />')
192 195 .addClass('close')
193 196 .html('&times;')
194 197 .click(function() {
195 198 widget_area.slideUp('', function(){ widget_subarea.html(''); });
196 199 })
197 200 .appendTo(widget_prompt);
198 201
199 202 var output = $('<div></div>');
200 203 cell.append(input).append(widget_area).append(output);
201 204 this.element = cell;
202 205 this.output_area = new outputarea.OutputArea({
203 206 selector: output,
204 207 prompt_area: true,
205 208 events: this.events,
206 209 keyboard_manager: this.keyboard_manager});
207 210 this.completer = new completer.Completer(this, this.events);
208 211 };
209 212
210 213 /** @method bind_events */
211 214 CodeCell.prototype.bind_events = function () {
212 215 Cell.prototype.bind_events.apply(this);
213 216 var that = this;
214 217
215 218 this.element.focusout(
216 219 function() { that.auto_highlight(); }
217 220 );
218 221 };
219 222
220 223
221 224 /**
222 225 * This method gets called in CodeMirror's onKeyDown/onKeyPress
223 226 * handlers and is used to provide custom key handling. Its return
224 227 * value is used to determine if CodeMirror should ignore the event:
225 228 * true = ignore, false = don't ignore.
226 229 * @method handle_codemirror_keyevent
227 230 */
228 231 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
229 232
230 233 var that = this;
231 234 // whatever key is pressed, first, cancel the tooltip request before
232 235 // they are sent, and remove tooltip if any, except for tab again
233 236 var tooltip_closed = null;
234 237 if (event.type === 'keydown' && event.which != keycodes.tab ) {
235 238 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
236 239 }
237 240
238 241 var cur = editor.getCursor();
239 242 if (event.keyCode === keycodes.enter){
240 243 this.auto_highlight();
241 244 }
242 245
243 246 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
244 247 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
245 248 // browser and keyboard layout !
246 249 // Pressing '(' , request tooltip, don't forget to reappend it
247 250 // The second argument says to hide the tooltip if the docstring
248 251 // is actually empty
249 252 this.tooltip.pending(that, true);
250 253 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
251 254 // If tooltip is active, cancel it. The call to
252 255 // remove_and_cancel_tooltip above doesn't pass, force=true.
253 256 // Because of this it won't actually close the tooltip
254 257 // if it is in sticky mode. Thus, we have to check again if it is open
255 258 // and close it with force=true.
256 259 if (!this.tooltip._hidden) {
257 260 this.tooltip.remove_and_cancel_tooltip(true);
258 261 }
259 262 // If we closed the tooltip, don't let CM or the global handlers
260 263 // handle this event.
261 264 event.codemirrorIgnore = true;
262 265 event.preventDefault();
263 266 return true;
264 267 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
265 268 if (editor.somethingSelected() || editor.getSelections().length !== 1){
266 269 var anchor = editor.getCursor("anchor");
267 270 var head = editor.getCursor("head");
268 271 if( anchor.line != head.line){
269 272 return false;
270 273 }
271 274 }
272 275 this.tooltip.request(that);
273 276 event.codemirrorIgnore = true;
274 277 event.preventDefault();
275 278 return true;
276 279 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
277 280 // Tab completion.
278 281 this.tooltip.remove_and_cancel_tooltip();
279 282
280 283 // completion does not work on multicursor, it might be possible though in some cases
281 284 if (editor.somethingSelected() || editor.getSelections().length > 1) {
282 285 return false;
283 286 }
284 287 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
285 288 if (pre_cursor.trim() === "") {
286 289 // Don't autocomplete if the part of the line before the cursor
287 290 // is empty. In this case, let CodeMirror handle indentation.
288 291 return false;
289 292 } else {
290 293 event.codemirrorIgnore = true;
291 294 event.preventDefault();
292 295 this.completer.startCompletion();
293 296 return true;
294 297 }
295 298 }
296 299
297 300 // keyboard event wasn't one of those unique to code cells, let's see
298 301 // if it's one of the generic ones (i.e. check edit mode shortcuts)
299 302 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
300 303 };
301 304
302 305 // Kernel related calls.
303 306
304 307 CodeCell.prototype.set_kernel = function (kernel) {
305 308 this.kernel = kernel;
306 309 };
307 310
308 311 /**
309 312 * Execute current code cell to the kernel
310 313 * @method execute
311 314 */
312 315 CodeCell.prototype.execute = function () {
313 316 if (!this.kernel || !this.kernel.is_connected()) {
314 317 console.log("Can't execute, kernel is not connected.");
315 318 return;
316 319 }
317 320
318 321 this.get_output_area().clear_output();
319 322
320 323 // Clear widget area
321 324 this.widget_subarea.html('');
322 325 this.widget_subarea.height('');
323 326 this.widget_area.height('');
324 327 this.widget_area.hide();
325 328
326 329 this.set_input_prompt('*');
327 330 this.element.addClass("running");
328 331 if (this.last_msg_id) {
329 332 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
330 333 }
331 334 var callbacks = this.get_callbacks();
332 335
333 336 var old_msg_id = this.last_msg_id;
334 337 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
335 338 if (old_msg_id) {
336 339 delete CodeCell.msg_cells[old_msg_id];
337 340 }
338 341 CodeCell.msg_cells[this.last_msg_id] = this;
339 342 this.render();
340 343 };
341 344
342 345 /**
343 346 * Construct the default callbacks for
344 347 * @method get_callbacks
345 348 */
346 349 CodeCell.prototype.get_callbacks = function () {
347 350 var that = this;
348 351 return {
349 352 shell : {
350 353 reply : $.proxy(this._handle_execute_reply, this),
351 354 payload : {
352 355 set_next_input : $.proxy(this._handle_set_next_input, this),
353 356 page : $.proxy(this._open_with_pager, this)
354 357 }
355 358 },
356 359 iopub : {
357 360 output : function() {
358 361 var output_area = that.get_output_area();
359 362 output_area.handle_output.apply(output_area, arguments);
360 363 },
361 364 clear_output : function() {
362 365 var output_area = that.get_output_area();
363 366 output_area.handle_clear_output.apply(output_area, arguments);
364 367 },
365 368 },
366 369 input : $.proxy(this._handle_input_request, this)
367 370 };
368 371 };
369 372
370 373 CodeCell.prototype._open_with_pager = function (payload) {
371 374 this.events.trigger('open_with_text.Pager', payload);
372 375 };
373 376
374 377 /**
375 378 * @method _handle_execute_reply
376 379 * @private
377 380 */
378 381 CodeCell.prototype._handle_execute_reply = function (msg) {
379 382 this.set_input_prompt(msg.content.execution_count);
380 383 this.element.removeClass("running");
381 384 this.events.trigger('set_dirty.Notebook', {value: true});
382 385 };
383 386
384 387 /**
385 388 * @method _handle_set_next_input
386 389 * @private
387 390 */
388 391 CodeCell.prototype._handle_set_next_input = function (payload) {
389 392 var data = {'cell': this, 'text': payload.text};
390 393 this.events.trigger('set_next_input.Notebook', data);
391 394 };
392 395
393 396 /**
394 397 * @method _handle_input_request
395 398 * @private
396 399 */
397 400 CodeCell.prototype._handle_input_request = function (msg) {
398 401 this.get_output_area().append_raw_input(msg);
399 402 };
400 403
401 404
402 405 // Basic cell manipulation.
403 406
404 407 CodeCell.prototype.select = function () {
405 408 var cont = Cell.prototype.select.apply(this);
406 409 if (cont) {
407 410 this.code_mirror.refresh();
408 411 this.auto_highlight();
409 412 }
410 413 return cont;
411 414 };
412 415
413 416 CodeCell.prototype.render = function () {
414 417 var cont = Cell.prototype.render.apply(this);
415 418 // Always execute, even if we are already in the rendered state
416 419 return cont;
417 420 };
418 421
419 422 CodeCell.prototype.select_all = function () {
420 423 var start = {line: 0, ch: 0};
421 424 var nlines = this.code_mirror.lineCount();
422 425 var last_line = this.code_mirror.getLine(nlines-1);
423 426 var end = {line: nlines-1, ch: last_line.length};
424 427 this.code_mirror.setSelection(start, end);
425 428 };
426 429
427 430
428 431 CodeCell.prototype.collapse_output = function () {
429 432 this.output_area.collapse();
430 433 };
431 434
432 435
433 436 CodeCell.prototype.expand_output = function () {
434 437 this.output_area.expand();
435 438 this.output_area.unscroll_area();
436 439 };
437 440
438 441 CodeCell.prototype.scroll_output = function () {
439 442 this.output_area.expand();
440 443 this.output_area.scroll_if_long();
441 444 };
442 445
443 446 CodeCell.prototype.toggle_output = function () {
444 447 this.output_area.toggle_output();
445 448 };
446 449
447 450 CodeCell.prototype.toggle_output_scroll = function () {
448 451 this.output_area.toggle_scroll();
449 452 };
450 453
451 454
452 455 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
453 456 var ns;
454 457 if (prompt_value === undefined || prompt_value === null) {
455 458 ns = "&nbsp;";
456 459 } else {
457 460 ns = encodeURIComponent(prompt_value);
458 461 }
459 462 return 'In&nbsp;[' + ns + ']:';
460 463 };
461 464
462 465 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
463 466 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
464 467 for(var i=1; i < lines_number; i++) {
465 468 html.push(['...:']);
466 469 }
467 470 return html.join('<br/>');
468 471 };
469 472
470 473 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
471 474
472 475
473 476 CodeCell.prototype.set_input_prompt = function (number) {
474 477 var nline = 1;
475 478 if (this.code_mirror !== undefined) {
476 479 nline = this.code_mirror.lineCount();
477 480 }
478 481 this.input_prompt_number = number;
479 482 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
480 483 // This HTML call is okay because the user contents are escaped.
481 484 this.element.find('div.input_prompt').html(prompt_html);
482 485 };
483 486
484 487
485 488 CodeCell.prototype.clear_input = function () {
486 489 this.code_mirror.setValue('');
487 490 };
488 491
489 492
490 493 CodeCell.prototype.get_text = function () {
491 494 return this.code_mirror.getValue();
492 495 };
493 496
494 497
495 498 CodeCell.prototype.set_text = function (code) {
496 499 return this.code_mirror.setValue(code);
497 500 };
498 501
499 502
500 503 CodeCell.prototype.clear_output = function (wait) {
501 504 this.get_output_area().clear_output(wait);
502 505 this.set_input_prompt();
503 506 };
504 507
505 508
506 509 // JSON serialization
507 510
508 511 CodeCell.prototype.fromJSON = function (data) {
509 512 Cell.prototype.fromJSON.apply(this, arguments);
510 513 if (data.cell_type === 'code') {
511 514 if (data.source !== undefined) {
512 515 this.set_text(data.source);
513 516 // make this value the starting point, so that we can only undo
514 517 // to this state, instead of a blank cell
515 518 this.code_mirror.clearHistory();
516 519 this.auto_highlight();
517 520 }
518 521 this.set_input_prompt(data.execution_count);
519 522 this.output_area.trusted = data.metadata.trusted || false;
520 523 this.output_area.fromJSON(data.outputs);
521 524 if (data.metadata.collapsed !== undefined) {
522 525 if (data.metadata.collapsed) {
523 526 this.collapse_output();
524 527 } else {
525 528 this.expand_output();
526 529 }
527 530 }
528 531 }
529 532 };
530 533
531 534
532 535 CodeCell.prototype.toJSON = function () {
533 536 var data = Cell.prototype.toJSON.apply(this);
534 537 data.source = this.get_text();
535 538 // is finite protect against undefined and '*' value
536 539 if (isFinite(this.input_prompt_number)) {
537 540 data.execution_count = this.input_prompt_number;
538 541 } else {
539 542 data.execution_count = null;
540 543 }
541 544 var outputs = this.output_area.toJSON();
542 545 data.outputs = outputs;
543 546 data.metadata.trusted = this.output_area.trusted;
544 547 data.metadata.collapsed = this.output_area.collapsed;
545 548 return data;
546 549 };
547 550
548 551 /**
549 552 * handle cell level logic when a cell is unselected
550 553 * @method unselect
551 554 * @return is the action being taken
552 555 */
553 556 CodeCell.prototype.unselect = function () {
554 557 var cont = Cell.prototype.unselect.apply(this);
555 558 if (cont) {
556 559 // When a code cell is usnelected, make sure that the corresponding
557 560 // tooltip and completer to that cell is closed.
558 561 this.tooltip.remove_and_cancel_tooltip(true);
559 562 if (this.completer !== null) {
560 563 this.completer.close();
561 564 }
562 565 }
563 566 return cont;
564 567 };
565 568
566 569 // Backwards compatability.
567 570 IPython.CodeCell = CodeCell;
568 571
569 572 return {'CodeCell': CodeCell};
570 573 });
General Comments 0
You need to be logged in to leave comments. Login now