##// END OF EJS Templates
Merge pull request #8048 from Carreau/i-heard-you-like-json-so-i-put-json-in-your-json-so-you-can-json-while-you-json...
Thomas Kluyver -
r20706:52de0163 merge
parent child Browse files
Show More
@@ -1,213 +1,211 b''
1 """MagicHelper - dockable widget showing magic commands for the MainWindow
1 """MagicHelper - dockable widget showing magic commands for the MainWindow
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Imports
8 # Imports
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 # stdlib imports
11 # stdlib imports
12 import json
12 import json
13 import re
13 import re
14 import sys
14 import sys
15
15
16 # System library imports
16 # System library imports
17 from IPython.external.qt import QtGui,QtCore
17 from IPython.external.qt import QtGui,QtCore
18
18
19 from IPython.core.magic import magic_escapes
19 from IPython.core.magic import magic_escapes
20
20
21 class MagicHelper(QtGui.QDockWidget):
21 class MagicHelper(QtGui.QDockWidget):
22 """MagicHelper - dockable widget for convenient search and running of
22 """MagicHelper - dockable widget for convenient search and running of
23 magic command for IPython QtConsole.
23 magic command for IPython QtConsole.
24 """
24 """
25
25
26 #---------------------------------------------------------------------------
26 #---------------------------------------------------------------------------
27 # signals
27 # signals
28 #---------------------------------------------------------------------------
28 #---------------------------------------------------------------------------
29
29
30 pasteRequested = QtCore.Signal(str, name = 'pasteRequested')
30 pasteRequested = QtCore.Signal(str, name = 'pasteRequested')
31 """This signal is emitted when user wants to paste selected magic
31 """This signal is emitted when user wants to paste selected magic
32 command into the command line.
32 command into the command line.
33 """
33 """
34
34
35 runRequested = QtCore.Signal(str, name = 'runRequested')
35 runRequested = QtCore.Signal(str, name = 'runRequested')
36 """This signal is emitted when user wants to execute selected magic command
36 """This signal is emitted when user wants to execute selected magic command
37 """
37 """
38
38
39 readyForUpdate = QtCore.Signal(name = 'readyForUpdate')
39 readyForUpdate = QtCore.Signal(name = 'readyForUpdate')
40 """This signal is emitted when MagicHelper is ready to be populated.
40 """This signal is emitted when MagicHelper is ready to be populated.
41 Since kernel querying mechanisms are out of scope of this class,
41 Since kernel querying mechanisms are out of scope of this class,
42 it expects its owner to invoke MagicHelper.populate_magic_helper()
42 it expects its owner to invoke MagicHelper.populate_magic_helper()
43 as a reaction on this event.
43 as a reaction on this event.
44 """
44 """
45
45
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47 # constructor
47 # constructor
48 #---------------------------------------------------------------------------
48 #---------------------------------------------------------------------------
49
49
50 def __init__(self, name, parent):
50 def __init__(self, name, parent):
51 super(MagicHelper, self).__init__(name, parent)
51 super(MagicHelper, self).__init__(name, parent)
52
52
53 self.data = None
53 self.data = None
54
54
55 class MinListWidget(QtGui.QListWidget):
55 class MinListWidget(QtGui.QListWidget):
56 """Temp class to overide the default QListWidget size hint
56 """Temp class to overide the default QListWidget size hint
57 in order to make MagicHelper narrow
57 in order to make MagicHelper narrow
58 """
58 """
59 def sizeHint(self):
59 def sizeHint(self):
60 s = QtCore.QSize()
60 s = QtCore.QSize()
61 s.setHeight(super(MinListWidget,self).sizeHint().height())
61 s.setHeight(super(MinListWidget,self).sizeHint().height())
62 s.setWidth(self.sizeHintForColumn(0))
62 s.setWidth(self.sizeHintForColumn(0))
63 return s
63 return s
64
64
65 # construct content
65 # construct content
66 self.frame = QtGui.QFrame()
66 self.frame = QtGui.QFrame()
67 self.search_label = QtGui.QLabel("Search:")
67 self.search_label = QtGui.QLabel("Search:")
68 self.search_line = QtGui.QLineEdit()
68 self.search_line = QtGui.QLineEdit()
69 self.search_class = QtGui.QComboBox()
69 self.search_class = QtGui.QComboBox()
70 self.search_list = MinListWidget()
70 self.search_list = MinListWidget()
71 self.paste_button = QtGui.QPushButton("Paste")
71 self.paste_button = QtGui.QPushButton("Paste")
72 self.run_button = QtGui.QPushButton("Run")
72 self.run_button = QtGui.QPushButton("Run")
73
73
74 # layout all the widgets
74 # layout all the widgets
75 main_layout = QtGui.QVBoxLayout()
75 main_layout = QtGui.QVBoxLayout()
76 search_layout = QtGui.QHBoxLayout()
76 search_layout = QtGui.QHBoxLayout()
77 search_layout.addWidget(self.search_label)
77 search_layout.addWidget(self.search_label)
78 search_layout.addWidget(self.search_line, 10)
78 search_layout.addWidget(self.search_line, 10)
79 main_layout.addLayout(search_layout)
79 main_layout.addLayout(search_layout)
80 main_layout.addWidget(self.search_class)
80 main_layout.addWidget(self.search_class)
81 main_layout.addWidget(self.search_list, 10)
81 main_layout.addWidget(self.search_list, 10)
82 action_layout = QtGui.QHBoxLayout()
82 action_layout = QtGui.QHBoxLayout()
83 action_layout.addWidget(self.paste_button)
83 action_layout.addWidget(self.paste_button)
84 action_layout.addWidget(self.run_button)
84 action_layout.addWidget(self.run_button)
85 main_layout.addLayout(action_layout)
85 main_layout.addLayout(action_layout)
86
86
87 self.frame.setLayout(main_layout)
87 self.frame.setLayout(main_layout)
88 self.setWidget(self.frame)
88 self.setWidget(self.frame)
89
89
90 # connect all the relevant signals to handlers
90 # connect all the relevant signals to handlers
91 self.visibilityChanged[bool].connect( self._update_magic_helper )
91 self.visibilityChanged[bool].connect( self._update_magic_helper )
92 self.search_class.activated[int].connect(
92 self.search_class.activated[int].connect(
93 self.class_selected
93 self.class_selected
94 )
94 )
95 self.search_line.textChanged[str].connect(
95 self.search_line.textChanged[str].connect(
96 self.search_changed
96 self.search_changed
97 )
97 )
98 self.search_list.itemDoubleClicked.connect(
98 self.search_list.itemDoubleClicked.connect(
99 self.paste_requested
99 self.paste_requested
100 )
100 )
101 self.paste_button.clicked[bool].connect(
101 self.paste_button.clicked[bool].connect(
102 self.paste_requested
102 self.paste_requested
103 )
103 )
104 self.run_button.clicked[bool].connect(
104 self.run_button.clicked[bool].connect(
105 self.run_requested
105 self.run_requested
106 )
106 )
107
107
108 #---------------------------------------------------------------------------
108 #---------------------------------------------------------------------------
109 # implementation
109 # implementation
110 #---------------------------------------------------------------------------
110 #---------------------------------------------------------------------------
111
111
112 def _update_magic_helper(self, visible):
112 def _update_magic_helper(self, visible):
113 """Start update sequence.
113 """Start update sequence.
114 This method is called when MagicHelper becomes visible. It clears
114 This method is called when MagicHelper becomes visible. It clears
115 the content and emits readyForUpdate signal. The owner of the
115 the content and emits readyForUpdate signal. The owner of the
116 instance is expected to invoke populate_magic_helper() when magic
116 instance is expected to invoke populate_magic_helper() when magic
117 info is available.
117 info is available.
118 """
118 """
119 if not visible or self.data is not None:
119 if not visible or self.data is not None:
120 return
120 return
121 self.data = {}
121 self.data = {}
122 self.search_class.clear()
122 self.search_class.clear()
123 self.search_class.addItem("Populating...")
123 self.search_class.addItem("Populating...")
124 self.search_list.clear()
124 self.search_list.clear()
125 self.readyForUpdate.emit()
125 self.readyForUpdate.emit()
126
126
127 def populate_magic_helper(self, data):
127 def populate_magic_helper(self, data):
128 """Expects data returned by lsmagics query from kernel.
128 """Expects data returned by lsmagics query from kernel.
129 Populates the search_class and search_list with relevant items.
129 Populates the search_class and search_list with relevant items.
130 """
130 """
131 self.search_class.clear()
131 self.search_class.clear()
132 self.search_list.clear()
132 self.search_list.clear()
133
133
134 self.data = json.loads(
134 self.data = data['data'].get('application/json', {})
135 data['data'].get('application/json', {})
136 )
137
135
138 self.search_class.addItem('All Magics', 'any')
136 self.search_class.addItem('All Magics', 'any')
139 classes = set()
137 classes = set()
140
138
141 for mtype in sorted(self.data):
139 for mtype in sorted(self.data):
142 subdict = self.data[mtype]
140 subdict = self.data[mtype]
143 for name in sorted(subdict):
141 for name in sorted(subdict):
144 classes.add(subdict[name])
142 classes.add(subdict[name])
145
143
146 for cls in sorted(classes):
144 for cls in sorted(classes):
147 label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
145 label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
148 self.search_class.addItem(label, cls)
146 self.search_class.addItem(label, cls)
149
147
150 self.filter_magic_helper('.', 'any')
148 self.filter_magic_helper('.', 'any')
151
149
152 def class_selected(self, index):
150 def class_selected(self, index):
153 """Handle search_class selection changes
151 """Handle search_class selection changes
154 """
152 """
155 item = self.search_class.itemData(index)
153 item = self.search_class.itemData(index)
156 regex = self.search_line.text()
154 regex = self.search_line.text()
157 self.filter_magic_helper(regex = regex, cls = item)
155 self.filter_magic_helper(regex = regex, cls = item)
158
156
159 def search_changed(self, search_string):
157 def search_changed(self, search_string):
160 """Handle search_line text changes.
158 """Handle search_line text changes.
161 The text is interpreted as a regular expression
159 The text is interpreted as a regular expression
162 """
160 """
163 item = self.search_class.itemData(
161 item = self.search_class.itemData(
164 self.search_class.currentIndex()
162 self.search_class.currentIndex()
165 )
163 )
166 self.filter_magic_helper(regex = search_string, cls = item)
164 self.filter_magic_helper(regex = search_string, cls = item)
167
165
168 def _get_current_search_item(self, item = None):
166 def _get_current_search_item(self, item = None):
169 """Retrieve magic command currently selected in the search_list
167 """Retrieve magic command currently selected in the search_list
170 """
168 """
171 text = None
169 text = None
172 if not isinstance(item, QtGui.QListWidgetItem):
170 if not isinstance(item, QtGui.QListWidgetItem):
173 item = self.search_list.currentItem()
171 item = self.search_list.currentItem()
174 text = item.text()
172 text = item.text()
175 return text
173 return text
176
174
177 def paste_requested(self, item = None):
175 def paste_requested(self, item = None):
178 """Emit pasteRequested signal with currently selected item text
176 """Emit pasteRequested signal with currently selected item text
179 """
177 """
180 text = self._get_current_search_item(item)
178 text = self._get_current_search_item(item)
181 if text is not None:
179 if text is not None:
182 self.pasteRequested.emit(text)
180 self.pasteRequested.emit(text)
183
181
184 def run_requested(self, item = None):
182 def run_requested(self, item = None):
185 """Emit runRequested signal with currently selected item text
183 """Emit runRequested signal with currently selected item text
186 """
184 """
187 text = self._get_current_search_item(item)
185 text = self._get_current_search_item(item)
188 if text is not None:
186 if text is not None:
189 self.runRequested.emit(text)
187 self.runRequested.emit(text)
190
188
191 def filter_magic_helper(self, regex, cls):
189 def filter_magic_helper(self, regex, cls):
192 """Update search_list with magic commands whose text match
190 """Update search_list with magic commands whose text match
193 regex and class match cls.
191 regex and class match cls.
194 If cls equals 'any' - any class matches.
192 If cls equals 'any' - any class matches.
195 """
193 """
196 if regex == "" or regex is None:
194 if regex == "" or regex is None:
197 regex = '.'
195 regex = '.'
198 if cls is None:
196 if cls is None:
199 cls = 'any'
197 cls = 'any'
200
198
201 self.search_list.clear()
199 self.search_list.clear()
202 for mtype in sorted(self.data):
200 for mtype in sorted(self.data):
203 subdict = self.data[mtype]
201 subdict = self.data[mtype]
204 prefix = magic_escapes[mtype]
202 prefix = magic_escapes[mtype]
205
203
206 for name in sorted(subdict):
204 for name in sorted(subdict):
207 mclass = subdict[name]
205 mclass = subdict[name]
208 pmagic = prefix + name
206 pmagic = prefix + name
209
207
210 if (re.match(regex, name) or re.match(regex, pmagic)) and \
208 if (re.match(regex, name) or re.match(regex, pmagic)) and \
211 (cls == 'any' or cls == mclass):
209 (cls == 'any' or cls == mclass):
212 self.search_list.addItem(pmagic)
210 self.search_list.addItem(pmagic)
213
211
General Comments 0
You need to be logged in to leave comments. Login now