magic_helper.py
213 lines
| 7.5 KiB
| text/x-python
|
PythonLexer
Dimitry Kloper
|
r16491 | """MagicHelper - dockable widget showing magic commands for the MainWindow | ||
Dimitry Kloper
|
r16483 | """ | ||
Dimitry Kloper
|
r16519 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Dimitry Kloper
|
r16483 | #----------------------------------------------------------------------------- | ||
# 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): | ||||
Dimitry Kloper
|
r16491 | """MagicHelper - dockable widget for convenient search and running of | ||
magic command for IPython QtConsole. | ||||
""" | ||||
#--------------------------------------------------------------------------- | ||||
# signals | ||||
#--------------------------------------------------------------------------- | ||||
Dimitry Kloper
|
r16483 | |||
Dimitry Kloper
|
r16532 | pasteRequested = QtCore.Signal(str, name = 'pasteRequested') | ||
Dimitry Kloper
|
r16491 | """This signal is emitted when user wants to paste selected magic | ||
command into the command line. | ||||
""" | ||||
Dimitry Kloper
|
r16532 | runRequested = QtCore.Signal(str, name = 'runRequested') | ||
Dimitry Kloper
|
r16491 | """This signal is emitted when user wants to execute selected magic command | ||
""" | ||||
Dimitry Kloper
|
r16532 | readyForUpdate = QtCore.Signal(name = 'readyForUpdate') | ||
Dimitry Kloper
|
r16491 | """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. | ||||
""" | ||||
Dimitry Kloper
|
r16483 | |||
#--------------------------------------------------------------------------- | ||||
Dimitry Kloper
|
r16491 | # constructor | ||
Dimitry Kloper
|
r16483 | #--------------------------------------------------------------------------- | ||
def __init__(self, name, parent): | ||||
super(MagicHelper, self).__init__(name, parent) | ||||
self.data = None | ||||
class MinListWidget(QtGui.QListWidget): | ||||
Dimitry Kloper
|
r16491 | """Temp class to overide the default QListWidget size hint | ||
in order to make MagicHelper narrow | ||||
""" | ||||
Dimitry Kloper
|
r16483 | def sizeHint(self): | ||
s = QtCore.QSize() | ||||
s.setHeight(super(MinListWidget,self).sizeHint().height()) | ||||
s.setWidth(self.sizeHintForColumn(0)) | ||||
return s | ||||
Dimitry Kloper
|
r16491 | # construct content | ||
Dimitry Kloper
|
r16483 | 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") | ||||
Dimitry Kloper
|
r16491 | # layout all the widgets | ||
Dimitry Kloper
|
r16483 | 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) | ||||
Dimitry Kloper
|
r16491 | # connect all the relevant signals to handlers | ||
self.visibilityChanged[bool].connect( self._update_magic_helper ) | ||||
Dimitry Kloper
|
r16483 | self.search_class.activated[int].connect( | ||
self.class_selected | ||||
) | ||||
self.search_line.textChanged[str].connect( | ||||
self.search_changed | ||||
) | ||||
self.search_list.itemDoubleClicked[QtGui.QListWidgetItem].connect( | ||||
self.paste_requested | ||||
) | ||||
self.paste_button.clicked[bool].connect( | ||||
self.paste_requested | ||||
) | ||||
self.run_button.clicked[bool].connect( | ||||
self.run_requested | ||||
) | ||||
Dimitry Kloper
|
r16491 | #--------------------------------------------------------------------------- | ||
# 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. | ||||
""" | ||||
Boris Egorov
|
r18172 | if not visible or self.data is not None: | ||
Dimitry Kloper
|
r16483 | return | ||
self.data = {} | ||||
self.search_class.clear() | ||||
self.search_class.addItem("Populating...") | ||||
Dimitry Kloper
|
r16491 | self.search_list.clear() | ||
Dimitry Kloper
|
r16484 | self.readyForUpdate.emit() | ||
Dimitry Kloper
|
r16483 | |||
def populate_magic_helper(self, data): | ||||
Dimitry Kloper
|
r16491 | """Expects data returned by lsmagics query from kernel. | ||
Populates the search_class and search_list with relevant items. | ||||
""" | ||||
Dimitry Kloper
|
r16483 | 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): | ||||
Dimitry Kloper
|
r16491 | """Handle search_class selection changes | ||
""" | ||||
Dimitry Kloper
|
r16483 | 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): | ||||
Dimitry Kloper
|
r16491 | """Handle search_line text changes. | ||
The text is interpreted as a regular expression | ||||
""" | ||||
Dimitry Kloper
|
r16483 | 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): | ||||
Dimitry Kloper
|
r16491 | """Retrieve magic command currently selected in the search_list | ||
""" | ||||
Dimitry Kloper
|
r16483 | text = None | ||
if not isinstance(item, QtGui.QListWidgetItem): | ||||
item = self.search_list.currentItem() | ||||
text = item.text() | ||||
return text | ||||
def paste_requested(self, item = None): | ||||
Dimitry Kloper
|
r16491 | """Emit pasteRequested signal with currently selected item text | ||
""" | ||||
Dimitry Kloper
|
r16483 | text = self._get_current_search_item(item) | ||
Boris Egorov
|
r18172 | if text is not None: | ||
Dimitry Kloper
|
r16483 | self.pasteRequested.emit(text) | ||
def run_requested(self, item = None): | ||||
Dimitry Kloper
|
r16491 | """Emit runRequested signal with currently selected item text | ||
""" | ||||
Dimitry Kloper
|
r16483 | text = self._get_current_search_item(item) | ||
Boris Egorov
|
r18172 | if text is not None: | ||
Dimitry Kloper
|
r16483 | self.runRequested.emit(text) | ||
def filter_magic_helper(self, regex, cls): | ||||
Dimitry Kloper
|
r16491 | """Update search_list with magic commands whose text match | ||
regex and class match cls. | ||||
If cls equals 'any' - any class matches. | ||||
""" | ||||
Boris Egorov
|
r18172 | if regex == "" or regex is None: | ||
Dimitry Kloper
|
r16483 | regex = '.' | ||
Boris Egorov
|
r18172 | if cls is None: | ||
Dimitry Kloper
|
r16483 | 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) | ||||