diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js
index 79b902e..9fc6d8e 100644
--- a/IPython/html/static/notebook/js/outputarea.js
+++ b/IPython/html/static/notebook/js/outputarea.js
@@ -268,39 +268,47 @@ var IPython = (function (IPython) {
});
return json;
};
-
+
OutputArea.prototype.append_output = function (json) {
this.expand();
+
+ // validate output data types
+ json = this.validate_output(json);
+
// Clear the output if clear is queued.
var needs_height_reset = false;
if (this.clear_queued) {
this.clear_output(false);
needs_height_reset = true;
}
-
- // validate output data types
- json = this.validate_output(json);
if (json.output_type === 'pyout') {
this.append_pyout(json);
} else if (json.output_type === 'pyerr') {
this.append_pyerr(json);
- } else if (json.output_type === 'display_data') {
- this.append_display_data(json);
} else if (json.output_type === 'stream') {
this.append_stream(json);
}
-
- this.outputs.push(json);
-
- // Only reset the height to automatic if the height is currently
- // fixed (done by wait=True flag on clear_output).
- if (needs_height_reset) {
- this.element.height('');
- }
+ // We must release the animation fixed height in a callback since Gecko
+ // (FireFox) doesn't render the image immediately as the data is
+ // available.
var that = this;
- setTimeout(function(){that.element.trigger('resize');}, 100);
+ var handle_appended = function ($el) {
+ // Only reset the height to automatic if the height is currently
+ // fixed (done by wait=True flag on clear_output).
+ if (needs_height_reset) {
+ that.element.height('');
+ }
+ that.element.trigger('resize');
+ };
+ if (json.output_type === 'display_data') {
+ this.append_display_data(json, handle_appended);
+ } else {
+ handle_appended();
+ }
+
+ this.outputs.push(json);
};
@@ -475,9 +483,9 @@ var IPython = (function (IPython) {
};
- OutputArea.prototype.append_display_data = function (json) {
+ OutputArea.prototype.append_display_data = function (json, handle_inserted) {
var toinsert = this.create_output_area();
- if (this.append_mime_type(json, toinsert)) {
+ if (this.append_mime_type(json, toinsert, handle_inserted)) {
this._safe_append(toinsert);
// If we just output latex, typeset it.
if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
@@ -494,7 +502,7 @@ var IPython = (function (IPython) {
'image/jpeg' : true
};
- OutputArea.prototype.append_mime_type = function (json, element) {
+ OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
for (var type_i in OutputArea.display_order) {
var type = OutputArea.display_order[type_i];
var append = OutputArea.append_map[type];
@@ -511,7 +519,14 @@ var IPython = (function (IPython) {
}
}
var md = json.metadata || {};
- var toinsert = append.apply(this, [value, md, element]);
+ var toinsert = append.apply(this, [value, md, element, handle_inserted]);
+ // Since only the png and jpeg mime types call the inserted
+ // callback, if the mime type is something other we must call the
+ // inserted callback only when the element is actually inserted
+ // into the DOM. Use a timeout of 0 to do this.
+ if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
+ setTimeout(handle_inserted, 0);
+ }
$([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
return toinsert;
}
@@ -611,10 +626,16 @@ var IPython = (function (IPython) {
if (width !== undefined) img.attr('width', width);
};
- var append_png = function (png, md, element) {
+ var append_png = function (png, md, element, handle_inserted) {
var type = 'image/png';
var toinsert = this.create_output_subarea(md, "output_png", type);
- var img = $("").attr('src','data:image/png;base64,'+png);
+ var img = $("");
+ if (handle_inserted !== undefined) {
+ img.on('load', function(){
+ handle_inserted(img);
+ });
+ }
+ img[0].src = 'data:image/png;base64,'+ png;
set_width_height(img, md, 'image/png');
this._dblclick_to_reset_size(img);
toinsert.append(img);
@@ -623,10 +644,16 @@ var IPython = (function (IPython) {
};
- var append_jpeg = function (jpeg, md, element) {
+ var append_jpeg = function (jpeg, md, element, handle_inserted) {
var type = 'image/jpeg';
var toinsert = this.create_output_subarea(md, "output_jpeg", type);
- var img = $("").attr('src','data:image/jpeg;base64,'+jpeg);
+ var img = $("");
+ if (handle_inserted !== undefined) {
+ img.on('load', function(){
+ handle_inserted(img);
+ });
+ }
+ img[0].src = 'data:image/jpeg;base64,'+ jpeg;
set_width_height(img, md, 'image/jpeg');
this._dblclick_to_reset_size(img);
toinsert.append(img);
@@ -748,7 +775,10 @@ var IPython = (function (IPython) {
this.clear_queued = false;
}
- // clear all, no need for logic
+ // Clear all
+ // Remove load event handlers from img tags because we don't want
+ // them to fire if the image is never added to the page.
+ this.element.find('img').off('load');
this.element.html("");
this.outputs = [];
this.trusted = true;
diff --git a/IPython/kernel/zmq/zmqshell.py b/IPython/kernel/zmq/zmqshell.py
index c2d044b..357b6e1 100644
--- a/IPython/kernel/zmq/zmqshell.py
+++ b/IPython/kernel/zmq/zmqshell.py
@@ -90,11 +90,7 @@ class ZMQDisplayPublisher(DisplayPublisher):
def clear_output(self, wait=False):
content = dict(wait=wait)
-
- print('\r', file=sys.stdout, end='')
- print('\r', file=sys.stderr, end='')
self._flush_streams()
-
self.session.send(
self.pub_socket, u'clear_output', content,
parent=self.parent_header, ident=self.topic,
diff --git a/IPython/qt/console/frontend_widget.py b/IPython/qt/console/frontend_widget.py
index 222db04..f345a80 100644
--- a/IPython/qt/console/frontend_widget.py
+++ b/IPython/qt/console/frontend_widget.py
@@ -197,6 +197,9 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
self._local_kernel = kw.get('local_kernel',
FrontendWidget._local_kernel)
+ # Whether or not a clear_output call is pending new output.
+ self._pending_clearoutput = False
+
#---------------------------------------------------------------------------
# 'ConsoleWidget' public interface
#---------------------------------------------------------------------------
@@ -339,6 +342,14 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
#---------------------------------------------------------------------------
# 'BaseFrontendMixin' abstract interface
#---------------------------------------------------------------------------
+ def _handle_clear_output(self, msg):
+ """Handle clear output messages."""
+ if not self._hidden and self._is_from_this_session(msg):
+ wait = msg['content'].get('wait', True)
+ if wait:
+ self._pending_clearoutput = True
+ else:
+ self.clear_output()
def _handle_complete_reply(self, rep):
""" Handle replies for tab completion.
@@ -520,6 +531,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
"""
self.log.debug("pyout: %s", msg.get('content', ''))
if not self._hidden and self._is_from_this_session(msg):
+ self.flush_clearoutput()
text = msg['content']['data']
self._append_plain_text(text + '\n', before_prompt=True)
@@ -528,13 +540,8 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
"""
self.log.debug("stream: %s", msg.get('content', ''))
if not self._hidden and self._is_from_this_session(msg):
- # Most consoles treat tabs as being 8 space characters. Convert tabs
- # to spaces so that output looks as expected regardless of this
- # widget's tab width.
- text = msg['content']['data'].expandtabs(8)
-
- self._append_plain_text(text, before_prompt=True)
- self._control.moveCursor(QtGui.QTextCursor.End)
+ self.flush_clearoutput()
+ self.append_stream(msg['content']['data'])
def _handle_shutdown_reply(self, msg):
""" Handle shutdown signal, only if from other console.
@@ -685,6 +692,29 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
before_prompt=True
)
+ def append_stream(self, text):
+ """Appends text to the output stream."""
+ # Most consoles treat tabs as being 8 space characters. Convert tabs
+ # to spaces so that output looks as expected regardless of this
+ # widget's tab width.
+ text = text.expandtabs(8)
+ self._append_plain_text(text, before_prompt=True)
+ self._control.moveCursor(QtGui.QTextCursor.End)
+
+ def flush_clearoutput(self):
+ """If a clearoutput is pending, execute it."""
+ if self._pending_clearoutput:
+ self._pending_clearoutput = False
+ self.clear_output()
+
+ def clear_output(self):
+ """Clears the current line of output."""
+ cursor = self._control.textCursor()
+ cursor.beginEditBlock()
+ cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
+ cursor.insertText('')
+ cursor.endEditBlock()
+
#---------------------------------------------------------------------------
# 'FrontendWidget' protected interface
#---------------------------------------------------------------------------
diff --git a/IPython/qt/console/ipython_widget.py b/IPython/qt/console/ipython_widget.py
index 962fd86..423877a 100644
--- a/IPython/qt/console/ipython_widget.py
+++ b/IPython/qt/console/ipython_widget.py
@@ -140,7 +140,6 @@ class IPythonWidget(FrontendWidget):
#---------------------------------------------------------------------------
# 'BaseFrontendMixin' abstract interface
#---------------------------------------------------------------------------
-
def _handle_complete_reply(self, rep):
""" Reimplemented to support IPython's improved completion machinery.
"""
@@ -223,6 +222,7 @@ class IPythonWidget(FrontendWidget):
"""
self.log.debug("pyout: %s", msg.get('content', ''))
if not self._hidden and self._is_from_this_session(msg):
+ self.flush_clearoutput()
content = msg['content']
prompt_number = content.get('execution_count', 0)
data = content['data']
@@ -250,6 +250,7 @@ class IPythonWidget(FrontendWidget):
# eventually will as this allows all frontends to monitor the display
# data. But we need to figure out how to handle this in the GUI.
if not self._hidden and self._is_from_this_session(msg):
+ self.flush_clearoutput()
source = msg['content']['source']
data = msg['content']['data']
metadata = msg['content']['metadata']
diff --git a/IPython/qt/console/rich_ipython_widget.py b/IPython/qt/console/rich_ipython_widget.py
index 4da9855..a21c45d 100644
--- a/IPython/qt/console/rich_ipython_widget.py
+++ b/IPython/qt/console/rich_ipython_widget.py
@@ -114,6 +114,7 @@ class RichIPythonWidget(IPythonWidget):
""" Overridden to handle rich data types, like SVG.
"""
if not self._hidden and self._is_from_this_session(msg):
+ self.flush_clearoutput()
content = msg['content']
prompt_number = content.get('execution_count', 0)
data = content['data']
@@ -140,6 +141,7 @@ class RichIPythonWidget(IPythonWidget):
""" Overridden to handle rich data types, like SVG.
"""
if not self._hidden and self._is_from_this_session(msg):
+ self.flush_clearoutput()
source = msg['content']['source']
data = msg['content']['data']
metadata = msg['content']['metadata']
diff --git a/IPython/terminal/console/interactiveshell.py b/IPython/terminal/console/interactiveshell.py
index f127ab3..dc897bf 100644
--- a/IPython/terminal/console/interactiveshell.py
+++ b/IPython/terminal/console/interactiveshell.py
@@ -43,6 +43,7 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
"""A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
_executing = False
_execution_state = Unicode('')
+ _pending_clearoutput = False
kernel_timeout = Float(60, config=True,
help="""Timeout for giving up on a kernel (in seconds).
@@ -241,13 +242,22 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
self._execution_state = sub_msg["content"]["execution_state"]
elif msg_type == 'stream':
if sub_msg["content"]["name"] == "stdout":
+ if self._pending_clearoutput:
+ print("\r", file=io.stdout, end="")
+ self._pending_clearoutput = False
print(sub_msg["content"]["data"], file=io.stdout, end="")
io.stdout.flush()
elif sub_msg["content"]["name"] == "stderr" :
+ if self._pending_clearoutput:
+ print("\r", file=io.stderr, end="")
+ self._pending_clearoutput = False
print(sub_msg["content"]["data"], file=io.stderr, end="")
io.stderr.flush()
elif msg_type == 'pyout':
+ if self._pending_clearoutput:
+ print("\r", file=io.stdout, end="")
+ self._pending_clearoutput = False
self.execution_count = int(sub_msg["content"]["execution_count"])
format_dict = sub_msg["content"]["data"]
self.handle_rich_data(format_dict)
@@ -267,6 +277,12 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
if 'text/plain' in data:
print(data['text/plain'])
+ elif msg_type == 'clear_output':
+ if sub_msg["content"]["wait"]:
+ self._pending_clearoutput = True
+ else:
+ print("\r", file=io.stdout, end="")
+
_imagemime = {
'image/png': 'png',
'image/jpeg': 'jpeg',