diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 12d5a62..96df318 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -256,11 +256,12 @@ class ConsoleWidget(QtGui.QPlainTextEdit): else: if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + intercepted = True if self._reading: + intercepted = False self._reading = False elif not self._executing: self.execute(interactive=True) - intercepted = True elif key == QtCore.Qt.Key_Up: if self._reading or not self._up_pressed(): @@ -677,6 +678,19 @@ class ConsoleWidget(QtGui.QPlainTextEdit): self.setReadOnly(True) self._prompt_finished_hook() + def _readline(self, prompt=''): + """ Read and return one line of input from the user. The trailing + newline is stripped. + """ + if not self.isVisible(): + raise RuntimeError('Cannot read a line if widget is not visible!') + + self._reading = True + self._show_prompt(prompt) + while self._reading: + QtCore.QCoreApplication.processEvents() + return self.input_buffer.rstrip('\n\r') + def _set_position(self, position): """ Convenience method to set the position of the cursor. """ @@ -691,11 +705,20 @@ class ConsoleWidget(QtGui.QPlainTextEdit): def _show_prompt(self, prompt=None): """ Writes a new prompt at the end of the buffer. If 'prompt' is not - specified, uses the previous prompt. + 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() + cursor.movePosition(QtGui.QTextCursor.Left, + QtGui.QTextCursor.KeepAnchor) + if str(cursor.selection().toPlainText()) not in '\r\n': + self.appendPlainText('\n') + if prompt is not None: self._prompt = prompt - self.appendPlainText('\n' + self._prompt) + self.appendPlainText(self._prompt) + self._prompt_pos = self._get_end_cursor().position() self._prompt_started() diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 929fb27..4c74557 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -60,7 +60,6 @@ class FrontendWidget(HistoryConsoleWidget): # ConsoleWidget protected variables. self._continuation_prompt = '... ' - self._prompt = '>>> ' # FrontendWidget protected variables. self._call_tip_widget = CallTipWidget(self) @@ -141,6 +140,17 @@ class FrontendWidget(HistoryConsoleWidget): return False #--------------------------------------------------------------------------- + # 'ConsoleWidget' protected interface + #--------------------------------------------------------------------------- + + def _show_prompt(self, prompt=None): + """ Reimplemented to set a default prompt. + """ + if prompt is None: + prompt = '>>> ' + super(FrontendWidget, self)._show_prompt(prompt) + + #--------------------------------------------------------------------------- # 'FrontendWidget' interface #--------------------------------------------------------------------------- @@ -261,6 +271,16 @@ class FrontendWidget(HistoryConsoleWidget): #------ Signal handlers ---------------------------------------------------- + def _started_channels(self): + """ Called when the kernel manager has started listening. + """ + self.clear() + + def _stopped_channels(self): + """ Called when the kernel manager has stopped listening. + """ + pass + def _document_contents_change(self, position, removed, added): """ Called whenever the document's content changes. Display a calltip if appropriate. @@ -272,6 +292,9 @@ class FrontendWidget(HistoryConsoleWidget): if position == self.textCursor().position(): self._call_tip() + def _handle_req(self): + print self._readline() + def _handle_sub(self, omsg): if self._hidden: return @@ -323,8 +346,4 @@ class FrontendWidget(HistoryConsoleWidget): if doc: self._call_tip_widget.show_docstring(doc) - def _started_channels(self): - self.clear() - - def _stopped_channels(self): - pass + diff --git a/IPython/zmq/kernel.py b/IPython/zmq/kernel.py index 1a7fb7d..1365e4c 100755 --- a/IPython/zmq/kernel.py +++ b/IPython/zmq/kernel.py @@ -28,6 +28,64 @@ from session import Session, Message, extract_header from completer import KernelCompleter +class InStream(object): + """ A file like object that reads from a 0MQ XREQ socket.""" + + def __init__(self, session, socket): + self.session = session + self.socket = socket + + def close(self): + self.socket = None + + def flush(self): + if self.socket is None: + raise ValueError(u'I/O operation on closed file') + + def isatty(self): + return False + + def next(self): + raise IOError('Seek not supported.') + + def read(self, size=-1): + raise NotImplementedError + + def readline(self, size=-1): + if self.socket is None: + raise ValueError(u'I/O operation on closed file') + else: + content = { u'size' : unicode(size) } + msg = self.session.msg(u'readline', content=content) + return self._request(msg) + + def readlines(self, size=-1): + raise NotImplementedError + + def seek(self, offset, whence=None): + raise IOError('Seek not supported.') + + def write(self, string): + raise IOError('Write not supported on a read only stream.') + + def writelines(self, sequence): + raise IOError('Write not supported on a read only stream.') + + def _request(self, msg): + self.socket.send_json(msg) + while True: + try: + reply = self.socket.recv_json(zmq.NOBLOCK) + except zmq.ZMQError, e: + if e.errno == zmq.EAGAIN: + pass + else: + raise + else: + break + return reply[u'content'][u'data'] + + class OutStream(object): """A file like object that publishes the stream to a 0MQ PUB socket.""" @@ -60,7 +118,7 @@ class OutStream(object): self._buffer_len = 0 self._buffer = [] - def isattr(self): + def isatty(self): return False def next(self): @@ -113,28 +171,6 @@ class DisplayHook(object): self.parent_header = extract_header(parent) -class RawInput(object): - - def __init__(self, session, socket): - self.session = session - self.socket = socket - - def __call__(self, prompt=None): - msg = self.session.msg(u'raw_input') - self.socket.send_json(msg) - while True: - try: - reply = self.socket.recv_json(zmq.NOBLOCK) - except zmq.ZMQError, e: - if e.errno == zmq.EAGAIN: - pass - else: - raise - else: - break - return reply[u'content'][u'data'] - - class Kernel(object): def __init__(self, session, reply_socket, pub_socket): @@ -319,7 +355,12 @@ def main(): pub_port = bind_port(pub_socket, namespace.ip, namespace.pub) print >>sys.__stdout__, "PUB Channel on port", pub_port + req_socket = context.socket(zmq.XREQ) + req_port = bind_port(req_socket, namespace.ip, namespace.req) + print >>sys.__stdout__, "REQ Channel on port", req_port + # Redirect input streams and set a display hook. + sys.stdin = InStream(session, req_socket) sys.stdout = OutStream(session, pub_socket, u'stdout') sys.stderr = OutStream(session, pub_socket, u'stderr') sys.displayhook = DisplayHook(session, pub_socket) diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index 2fb63e0..1941123 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -218,7 +218,6 @@ class XReqSocketChannel(ZmqSocketChannel): ------- The msg_id of the message sent. """ - print oname content = dict(oname=oname) msg = self.session.msg('object_info_request', content) self._queue_request(msg)