##// END OF EJS Templates
bump pyzmq version dependency to 13...
bump pyzmq version dependency to 13 pyzmq < 13 doesn't work with tornado >= 3, which we also require. This results in clearer error messages when pyzmq is too old.

File last commit:

r19486:9018a28a
r20354:4b151608
Show More
magic_helper.py
213 lines | 7.4 KiB | text/x-python | PythonLexer
"""MagicHelper - dockable widget showing magic commands for the MainWindow
"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# stdlib imports
import json
import re
import sys
# System library imports
from IPython.external.qt import QtGui,QtCore
from IPython.core.magic import magic_escapes
class MagicHelper(QtGui.QDockWidget):
"""MagicHelper - dockable widget for convenient search and running of
magic command for IPython QtConsole.
"""
#---------------------------------------------------------------------------
# signals
#---------------------------------------------------------------------------
pasteRequested = QtCore.Signal(str, name = 'pasteRequested')
"""This signal is emitted when user wants to paste selected magic
command into the command line.
"""
runRequested = QtCore.Signal(str, name = 'runRequested')
"""This signal is emitted when user wants to execute selected magic command
"""
readyForUpdate = QtCore.Signal(name = 'readyForUpdate')
"""This signal is emitted when MagicHelper is ready to be populated.
Since kernel querying mechanisms are out of scope of this class,
it expects its owner to invoke MagicHelper.populate_magic_helper()
as a reaction on this event.
"""
#---------------------------------------------------------------------------
# constructor
#---------------------------------------------------------------------------
def __init__(self, name, parent):
super(MagicHelper, self).__init__(name, parent)
self.data = None
class MinListWidget(QtGui.QListWidget):
"""Temp class to overide the default QListWidget size hint
in order to make MagicHelper narrow
"""
def sizeHint(self):
s = QtCore.QSize()
s.setHeight(super(MinListWidget,self).sizeHint().height())
s.setWidth(self.sizeHintForColumn(0))
return s
# construct content
self.frame = QtGui.QFrame()
self.search_label = QtGui.QLabel("Search:")
self.search_line = QtGui.QLineEdit()
self.search_class = QtGui.QComboBox()
self.search_list = MinListWidget()
self.paste_button = QtGui.QPushButton("Paste")
self.run_button = QtGui.QPushButton("Run")
# layout all the widgets
main_layout = QtGui.QVBoxLayout()
search_layout = QtGui.QHBoxLayout()
search_layout.addWidget(self.search_label)
search_layout.addWidget(self.search_line, 10)
main_layout.addLayout(search_layout)
main_layout.addWidget(self.search_class)
main_layout.addWidget(self.search_list, 10)
action_layout = QtGui.QHBoxLayout()
action_layout.addWidget(self.paste_button)
action_layout.addWidget(self.run_button)
main_layout.addLayout(action_layout)
self.frame.setLayout(main_layout)
self.setWidget(self.frame)
# connect all the relevant signals to handlers
self.visibilityChanged[bool].connect( self._update_magic_helper )
self.search_class.activated[int].connect(
self.class_selected
)
self.search_line.textChanged[str].connect(
self.search_changed
)
self.search_list.itemDoubleClicked.connect(
self.paste_requested
)
self.paste_button.clicked[bool].connect(
self.paste_requested
)
self.run_button.clicked[bool].connect(
self.run_requested
)
#---------------------------------------------------------------------------
# implementation
#---------------------------------------------------------------------------
def _update_magic_helper(self, visible):
"""Start update sequence.
This method is called when MagicHelper becomes visible. It clears
the content and emits readyForUpdate signal. The owner of the
instance is expected to invoke populate_magic_helper() when magic
info is available.
"""
if not visible or self.data is not None:
return
self.data = {}
self.search_class.clear()
self.search_class.addItem("Populating...")
self.search_list.clear()
self.readyForUpdate.emit()
def populate_magic_helper(self, data):
"""Expects data returned by lsmagics query from kernel.
Populates the search_class and search_list with relevant items.
"""
self.search_class.clear()
self.search_list.clear()
self.data = json.loads(
data['data'].get('application/json', {})
)
self.search_class.addItem('All Magics', 'any')
classes = set()
for mtype in sorted(self.data):
subdict = self.data[mtype]
for name in sorted(subdict):
classes.add(subdict[name])
for cls in sorted(classes):
label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
self.search_class.addItem(label, cls)
self.filter_magic_helper('.', 'any')
def class_selected(self, index):
"""Handle search_class selection changes
"""
item = self.search_class.itemData(index)
regex = self.search_line.text()
self.filter_magic_helper(regex = regex, cls = item)
def search_changed(self, search_string):
"""Handle search_line text changes.
The text is interpreted as a regular expression
"""
item = self.search_class.itemData(
self.search_class.currentIndex()
)
self.filter_magic_helper(regex = search_string, cls = item)
def _get_current_search_item(self, item = None):
"""Retrieve magic command currently selected in the search_list
"""
text = None
if not isinstance(item, QtGui.QListWidgetItem):
item = self.search_list.currentItem()
text = item.text()
return text
def paste_requested(self, item = None):
"""Emit pasteRequested signal with currently selected item text
"""
text = self._get_current_search_item(item)
if text is not None:
self.pasteRequested.emit(text)
def run_requested(self, item = None):
"""Emit runRequested signal with currently selected item text
"""
text = self._get_current_search_item(item)
if text is not None:
self.runRequested.emit(text)
def filter_magic_helper(self, regex, cls):
"""Update search_list with magic commands whose text match
regex and class match cls.
If cls equals 'any' - any class matches.
"""
if regex == "" or regex is None:
regex = '.'
if cls is None:
cls = 'any'
self.search_list.clear()
for mtype in sorted(self.data):
subdict = self.data[mtype]
prefix = magic_escapes[mtype]
for name in sorted(subdict):
mclass = subdict[name]
pmagic = prefix + name
if (re.match(regex, name) or re.match(regex, pmagic)) and \
(cls == 'any' or cls == mclass):
self.search_list.addItem(pmagic)