diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index ae0afda..7d06188 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -23,6 +23,7 @@ define([ 'notebook/js/codemirror-ipython' ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) { "use strict"; + var Cell = cell.Cell; /* local util for codemirror */ @@ -79,6 +80,23 @@ define([ this.input_prompt_number = null; this.celltoolbar = null; this.output_area = null; + // Keep a stack of the 'active' output areas (where active means the + // output area that recieves output). When a user activates an output + // area, it gets pushed to the stack. Then, when the output area is + // deactivated, it's popped from the stack. When the stack is empty, + // the cell's output area is used. + this.active_output_areas = []; + var that = this; + Object.defineProperty(this, 'active_output_area', { + get: function() { + if (that.active_output_areas && that.active_output_areas.length > 0) { + return that.active_output_areas[that.active_output_areas.length-1]; + } else { + return that.output_area; + } + }, + }); + this.last_msg_id = null; this.completer = null; @@ -91,8 +109,6 @@ define([ // Attributes we want to override in this subclass. this.cell_type = "code"; - - var that = this; this.element.focusout( function() { that.auto_highlight(); } ); @@ -118,6 +134,23 @@ define([ CodeCell.prototype = Object.create(Cell.prototype); /** + * @method push_output_area + */ + CodeCell.prototype.push_output_area = function (output_area) { + this.active_output_areas.push(output_area); + }; + + /** + * @method pop_output_area + */ + CodeCell.prototype.pop_output_area = function (output_area) { + var index = this.active_output_areas.lastIndexOf(output_area); + if (index > -1) { + this.active_output_areas.splice(index, 1); + } + }; + + /** * @method auto_highlight */ CodeCell.prototype.auto_highlight = function () { @@ -282,8 +315,8 @@ define([ console.log("Can't execute, kernel is not connected."); return; } - - this.output_area.clear_output(); + + this.active_output_area.clear_output(); // Clear widget area this.widget_subarea.html(''); @@ -312,6 +345,7 @@ define([ * @method get_callbacks */ CodeCell.prototype.get_callbacks = function () { + var that = this; return { shell : { reply : $.proxy(this._handle_execute_reply, this), @@ -321,8 +355,12 @@ define([ } }, iopub : { - output : $.proxy(this.output_area.handle_output, this.output_area), - clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area), + output : function() { + that.active_output_area.handle_output.apply(that.active_output_area, arguments); + }, + clear_output : function() { + that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments); + }, }, input : $.proxy(this._handle_input_request, this) }; @@ -356,7 +394,7 @@ define([ * @private */ CodeCell.prototype._handle_input_request = function (msg) { - this.output_area.append_raw_input(msg); + this.active_output_area.append_raw_input(msg); }; @@ -459,7 +497,7 @@ define([ CodeCell.prototype.clear_output = function (wait) { - this.output_area.clear_output(wait); + this.active_output_area.clear_output(wait); this.set_input_prompt(); }; diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index ae56b94..0471988 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -400,6 +400,9 @@ define([ this._append_javascript_error(err, subarea); this.element.append(toinsert); } + + // Notify others of changes. + this.element.trigger('changed'); }; @@ -832,6 +835,10 @@ define([ // them to fire if the image is never added to the page. this.element.find('img').off('load'); this.element.html(""); + + // Notify others of changes. + this.element.trigger('changed'); + this.outputs = []; this.trusted = true; this.unscroll_area(); diff --git a/IPython/html/static/widgets/js/init.js b/IPython/html/static/widgets/js/init.js index cfbd134..a65ffe4 100644 --- a/IPython/html/static/widgets/js/init.js +++ b/IPython/html/static/widgets/js/init.js @@ -9,6 +9,7 @@ define([ "widgets/js/widget_float", "widgets/js/widget_image", "widgets/js/widget_int", + "widgets/js/widget_output", "widgets/js/widget_selection", "widgets/js/widget_selectioncontainer", "widgets/js/widget_string", diff --git a/IPython/html/static/widgets/js/widget_output.js b/IPython/html/static/widgets/js/widget_output.js new file mode 100644 index 0000000..a9f26f6 --- /dev/null +++ b/IPython/html/static/widgets/js/widget_output.js @@ -0,0 +1,57 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + "widgets/js/widget", + "jquery", + 'notebook/js/outputarea', +], function(widget, $, outputarea) { + 'use strict'; + + var OutputView = widget.DOMWidgetView.extend({ + initialize: function (parameters) { + // Public constructor + OutputView.__super__.initialize.apply(this, [parameters]); + this.model.on('msg:custom', this._handle_route_msg, this); + }, + + render: function(){ + // Called when view is rendered. + this.output_area = new outputarea.OutputArea({ + selector: this.$el, + prompt_area: false, + events: this.model.widget_manager.notebook.events, + keyboard_manager: this.model.widget_manager.keyboard_manager }); + + // Make output area reactive. + var that = this; + this.output_area.element.on('changed', function() { + that.model.set('contents', that.output_area.element.html()); + }); + this.model.on('change:contents', function(){ + var html = this.model.get('contents'); + if (this.output_area.element.html() != html) { + this.output_area.element.html(html); + } + }, this); + + // Set initial contents. + this.output_area.element.html(this.model.get('contents')); + }, + + _handle_route_msg: function(content) { + var cell = this.options.cell; + if (content && cell) { + if (content.method == 'push') { + cell.push_output_area(this.output_area); + } else if (content.method == 'pop') { + cell.pop_output_area(this.output_area); + } + } + }, + }); + + return { + 'OutputView': OutputView, + }; +}); diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py index 8766356..d62a3d2 100644 --- a/IPython/html/widgets/__init__.py +++ b/IPython/html/widgets/__init__.py @@ -6,6 +6,7 @@ from .widget_box import Box, Popup, FlexBox, HBox, VBox from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider from .widget_image import Image from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider +from .widget_output import Output from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select from .widget_selectioncontainer import Tab, Accordion from .widget_string import HTML, Latex, Text, Textarea diff --git a/IPython/html/widgets/widget_output.py b/IPython/html/widgets/widget_output.py new file mode 100644 index 0000000..664bc74 --- /dev/null +++ b/IPython/html/widgets/widget_output.py @@ -0,0 +1,50 @@ +"""Output class. + +Represents a widget that can be used to display output within the widget area. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from .widget import DOMWidget +import sys +from IPython.utils.traitlets import Unicode, List +from IPython.display import clear_output +from IPython.testing.skipdoctest import skip_doctest + +@skip_doctest +class Output(DOMWidget): + """Widget used as a context manager to display output. + + This widget can capture and display stdout, stderr, and rich output. To use + it, create an instance of it and display it. Then use it as a context + manager. Any output produced while in it's context will be captured and + displayed in it instead of the standard output area. + + Example + from IPython.html import widgets + from IPython.display import display + out = widgets.Output() + display(out) + + print('prints to output area') + + with out: + print('prints to output widget')""" + _view_name = Unicode('OutputView', sync=True) + + def clear_output(self, *pargs, **kwargs): + with self: + clear_output(*pargs, **kwargs) + + def __enter__(self): + self._flush() + self.send({'method': 'push'}) + + def __exit__(self, exception_type, exception_value, traceback): + self._flush() + self.send({'method': 'pop'}) + + def _flush(self): + sys.stdout.flush() + sys.stderr.flush()