diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 03a1b7f..aa7a897 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -715,7 +715,7 @@ class ConsoleWidget(QtGui.QPlainTextEdit): 'is not visible!') self._reading = True - self._show_prompt(prompt) + self._show_prompt(prompt, newline=False) if callback is None: self._reading_callback = None @@ -739,18 +739,25 @@ class ConsoleWidget(QtGui.QPlainTextEdit): """ self.setTextCursor(self._get_selection_cursor(start, end)) - def _show_prompt(self, prompt=None): - """ Writes a new prompt at the end of the buffer. If 'prompt' is not - specified, the previous prompt is used. - """ - # Use QTextDocumentFragment intermediate object because it strips - # out the Unicode line break characters that Qt insists on inserting. - cursor = self._get_end_cursor() - if cursor.position() > 0: - cursor.movePosition(QtGui.QTextCursor.Left, - QtGui.QTextCursor.KeepAnchor) - if str(cursor.selection().toPlainText()) != '\n': - self.appendPlainText('\n') + def _show_prompt(self, prompt=None, newline=True): + """ Writes a new prompt at the end of the buffer. + + Parameters + ---------- + prompt : str, optional + The prompt to show. If not specified, the previous prompt is used. + + newline : bool, optional (default True) + If set, a new line will be written before showing the prompt if + there is not already a newline at the end of the buffer. + """ + if newline: + cursor = self._get_end_cursor() + if cursor.position() > 0: + cursor.movePosition(QtGui.QTextCursor.Left, + QtGui.QTextCursor.KeepAnchor) + if str(cursor.selection().toPlainText()) != '\n': + self.appendPlainText('\n') if prompt is not None: self._prompt = prompt diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 1119ef8..87e4de7 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -143,12 +143,12 @@ class FrontendWidget(HistoryConsoleWidget): # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- - def _show_prompt(self, prompt=None): + def _show_prompt(self, prompt=None, newline=True): """ Reimplemented to set a default prompt. """ if prompt is None: prompt = '>>> ' - super(FrontendWidget, self)._show_prompt(prompt) + super(FrontendWidget, self)._show_prompt(prompt, newline) #--------------------------------------------------------------------------- # 'FrontendWidget' interface @@ -297,6 +297,10 @@ class FrontendWidget(HistoryConsoleWidget): self._call_tip() def _handle_req(self, req): + # Make sure that all output from the SUB channel has been processed + # before entering readline mode. + self.kernel_manager.sub_channel.flush() + def callback(line): self.kernel_manager.rep_channel.readline(line) self._readline(callback=callback) diff --git a/IPython/zmq/kernel.py b/IPython/zmq/kernel.py index 17399ae..98ff925 100755 --- a/IPython/zmq/kernel.py +++ b/IPython/zmq/kernel.py @@ -13,11 +13,11 @@ Things to do: # Standard library imports. import __builtin__ +from code import CommandCompiler import os import sys import time import traceback -from code import CommandCompiler # System library imports. import zmq @@ -40,7 +40,7 @@ class InStream(object): def flush(self): if self.socket is None: - raise ValueError(u'I/O operation on closed file') + raise ValueError('I/O operation on closed file') def isatty(self): return False @@ -49,19 +49,33 @@ class InStream(object): raise IOError('Seek not supported.') def read(self, size=-1): - raise NotImplementedError + # FIXME: Do we want another request for this? + string = '\n'.join(self.readlines()) + return self._truncate(string, size) def readline(self, size=-1): if self.socket is None: - raise ValueError(u'I/O operation on closed file') + raise ValueError('I/O operation on closed file') else: content = dict(size=size) msg = self.session.msg('readline_request', content=content) reply = self._request(msg) - return reply['content']['line'] + line = reply['content']['line'] + return self._truncate(line, size) - def readlines(self, size=-1): - raise NotImplementedError + def readlines(self, sizehint=-1): + # Sizehint is ignored, as is permitted. + if self.socket is None: + raise ValueError('I/O operation on closed file') + else: + lines = [] + while True: + line = self.readline() + if line: + lines.append(line) + else: + break + return lines def seek(self, offset, whence=None): raise IOError('Seek not supported.') @@ -73,6 +87,11 @@ class InStream(object): raise IOError('Write not supported on a read only stream.') def _request(self, msg): + # Flush output before making the request. This ensures, for example, + # that raw_input(prompt) actually gets a prompt written. + sys.stderr.flush() + sys.stdout.flush() + self.socket.send_json(msg) while True: try: @@ -86,6 +105,15 @@ class InStream(object): break return reply + def _truncate(self, string, size): + if size >= 0: + if isinstance(string, str): + return string[:size] + elif isinstance(string, unicode): + encoded = string.encode('utf-8')[:size] + return encoded.decode('utf-8', 'ignore') + return string + class OutStream(object): """A file like object that publishes the stream to a 0MQ PUB socket."""