Skip to content

Commit

Permalink
use is_complete request to indicate complete input
Browse files Browse the repository at this point in the history
instead of IPython InputSplitter
  • Loading branch information
minrk committed Jul 2, 2015
1 parent f948a83 commit c5f2ea4
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 38 deletions.
23 changes: 21 additions & 2 deletions qtconsole/base_frontend_mixin.py
@@ -1,5 +1,6 @@
""" Defines a convenient mix-in class for implementing Qt frontends.
"""
"""Defines a convenient mix-in class for implementing Qt frontends."""

from jupyter_client import BlockingKernelClient

class BaseFrontendMixin(object):
""" A mix-in class for implementing Qt frontends.
Expand All @@ -14,6 +15,7 @@ class BaseFrontendMixin(object):
#---------------------------------------------------------------------------
_kernel_client = None
_kernel_manager = None
_blocking_client = None

@property
def kernel_client(self):
Expand Down Expand Up @@ -62,6 +64,23 @@ def kernel_client(self, kernel_client):
# we connected.
if kernel_client.channels_running:
self._started_channels()

def _make_blocking_client(self):
kc = self.kernel_client
if kc is None:
return

info = kc.get_connection_info()
bc = BlockingKernelClient(**info)
bc.session.key = kc.session.key
bc.shell_channel.start()
self._blocking_client = bc

@property
def blocking_client(self):
if self._blocking_client is None:
self._make_blocking_client()
return self._blocking_client

@property
def kernel_manager(self):
Expand Down
48 changes: 22 additions & 26 deletions qtconsole/frontend_widget.py
Expand Up @@ -8,17 +8,18 @@
from collections import namedtuple
import sys
import uuid
try:
from queue import Empty
except ImportError:
from Queue import Empty

from qtconsole import qt
from qtconsole.qt import QtCore, QtGui
from ipython_genutils import py3compat
from ipython_genutils.importstring import import_item

from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
from IPython.core.inputtransformer import classic_prompt
from IPython.core.oinspect import call_tip
from qtconsole.base_frontend_mixin import BaseFrontendMixin
from traitlets import Any, Bool, Instance, Unicode, DottedObjectName
from traitlets import Any, Bool, Float, Instance, Unicode, DottedObjectName
from .bracket_matcher import BracketMatcher
from .call_tip_widget import CallTipWidget
from .history_console_widget import HistoryConsoleWidget
Expand Down Expand Up @@ -109,6 +110,9 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
lexer_class = DottedObjectName(config=True,
help="The pygments lexer class to use."
)
is_complete_timeout = Float(0.25, config=True,
help="Seconds to wait for is_complete replies from the kernel."
)
def _lexer_class_changed(self, name, old, new):
lexer_class = import_item(new)
self.lexer = lexer_class()
Expand All @@ -135,15 +139,9 @@ def _lexer_default(self):
# Emitted when an exit request has been received from the kernel.
exit_requested = QtCore.Signal(object)

# Protected class variables.
_prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
logical_line_transforms=[],
python_line_transforms=[],
)
_CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
_CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
_ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
_input_splitter_class = InputSplitter
_local_kernel = False
_highlighter = Instance(FrontendHighlighter, allow_none=True)

Expand All @@ -167,11 +165,10 @@ def __init__(self, *args, **kw):
self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
self._hidden = False
self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
self._input_splitter = self._input_splitter_class()
self._kernel_manager = None
self._kernel_client = None
self._request_info = {}
self._request_info['execute'] = {};
self._request_info['execute'] = {}
self._callback_dict = {}
self._display_banner = True

Expand Down Expand Up @@ -217,7 +214,6 @@ def copy(self):
text = self._control.textCursor().selection().toPlainText()
if text:
was_newline = text[-1] == '\n'
text = self._prompt_transformer.transform_cell(text)
if not was_newline: # user doesn't need newline
text = text[:-1]
QtGui.QApplication.clipboard().setText(text)
Expand All @@ -233,14 +229,18 @@ def _is_complete(self, source, interactive):
prompt created. When triggered by an Enter/Return key press,
'interactive' is True; otherwise, it is False.
"""
self._input_splitter.reset()
try:
complete = self._input_splitter.push(source)
except SyntaxError:
return True
if interactive:
complete = not self._input_splitter.push_accepts_more()
return complete
kc = self.blocking_client
msg_id = kc.is_complete(source)

This comment has been minimized.

Copy link
@rabryan

rabryan Jul 23, 2015

I don't believe this can work when using an InProcessKernel. I'm currently using one with a console embedded in a qt app and this commit broke is_complete() functionality since kc.shell_channel.get_msg() always raises an Empty exception

while True:
try:
reply = kc.shell_channel.get_msg(block=True, timeout=self.is_complete_timeout)
except Empty:
# assume incomplete output if we get no reply in time
return False
if reply['parent_header'].get('msg_id', None) == msg_id:
return reply['content']['status'] == 'complete'
else:
continue

def _execute(self, source, hidden):
""" Execute 'source'. If 'hidden', do not show any output.
Expand All @@ -263,10 +263,6 @@ def _prompt_finished_hook(self):
""" Called immediately after a prompt is finished, i.e. when some input
will be processed and a new prompt displayed.
"""
# Flush all state from the input splitter so the next round of
# reading input starts with a clean buffer.
self._input_splitter.reset()

if not self._reading:
self._highlighter.highlighting_on = False

Expand Down Expand Up @@ -344,7 +340,7 @@ def _insert_continuation_prompt(self, cursor):
""" Reimplemented for auto-indentation.
"""
super(FrontendWidget, self)._insert_continuation_prompt(cursor)
cursor.insertText(' ' * self._input_splitter.indent_spaces)
cursor.insertText(' ' * 4)

#---------------------------------------------------------------------------
# 'BaseFrontendMixin' abstract interface
Expand Down
9 changes: 0 additions & 9 deletions qtconsole/ipython_widget.py
Expand Up @@ -16,9 +16,7 @@

from qtconsole.qt import QtCore, QtGui

from IPython.core.inputsplitter import IPythonInputSplitter
from qtconsole import __version__
from IPython.core.inputtransformer import ipy_prompt
from traitlets import Bool, Unicode
from .frontend_widget import FrontendWidget
from . import styles
Expand Down Expand Up @@ -94,13 +92,6 @@ class IPythonWidget(FrontendWidget):
output_sep = Unicode(default_output_sep, config=True)
output_sep2 = Unicode(default_output_sep2, config=True)

# FrontendWidget protected class variables.
_input_splitter_class = IPythonInputSplitter
_prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
logical_line_transforms=[],
python_line_transforms=[],
)

# IPythonWidget protected class variables.
_PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
_payload_source_edit = 'edit_magic'
Expand Down
2 changes: 1 addition & 1 deletion qtconsole/qtconsoleapp.py
Expand Up @@ -10,6 +10,7 @@
import os
import signal
import sys
from warnings import warn

# If run on Windows, install an exception hook which pops up a
# message box. Pythonw.exe hides the console, so without this
Expand Down Expand Up @@ -43,7 +44,6 @@ def gui_excepthook(exctype, value, tb):

from qtconsole.qt import QtCore, QtGui

from traitlets.config import Configurable
from traitlets.config.application import boolean_flag
from traitlets.config.application import catch_config_error
from qtconsole.ipython_widget import IPythonWidget
Expand Down

0 comments on commit c5f2ea4

Please sign in to comment.