##// END OF EJS Templates
Setting hidden=True for FrontWidget's 'execute_source' works again.
epatters -
Show More
@@ -1,320 +1,324
1 1 # Standard library imports
2 2 from codeop import CommandCompiler
3 3 from threading import Thread
4 4 import time
5 5 import types
6 6
7 7 # System library imports
8 8 from pygments.lexers import PythonLexer
9 9 from PyQt4 import QtCore, QtGui
10 10 import zmq
11 11
12 12 # Local imports
13 13 from call_tip_widget import CallTipWidget
14 14 from completion_lexer import CompletionLexer
15 15 from console_widget import HistoryConsoleWidget
16 16 from pygments_highlighter import PygmentsHighlighter
17 17
18 18
19 19 class FrontendHighlighter(PygmentsHighlighter):
20 20 """ A Python PygmentsHighlighter that can be turned on and off and which
21 21 knows about continuation prompts.
22 22 """
23 23
24 24 def __init__(self, frontend):
25 25 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
26 26 self._current_offset = 0
27 27 self._frontend = frontend
28 28 self.highlighting_on = False
29 29
30 30 def highlightBlock(self, qstring):
31 31 """ Highlight a block of text. Reimplemented to highlight selectively.
32 32 """
33 33 if self.highlighting_on:
34 34 for prompt in (self._frontend._prompt,
35 35 self._frontend.continuation_prompt):
36 36 if qstring.startsWith(prompt):
37 37 qstring.remove(0, len(prompt))
38 38 self._current_offset = len(prompt)
39 39 break
40 40 PygmentsHighlighter.highlightBlock(self, qstring)
41 41
42 42 def setFormat(self, start, count, format):
43 43 """ Reimplemented to avoid highlighting continuation prompts.
44 44 """
45 45 start += self._current_offset
46 46 PygmentsHighlighter.setFormat(self, start, count, format)
47 47
48 48
49 49 class FrontendWidget(HistoryConsoleWidget):
50 50 """ A Qt frontend for an IPython kernel.
51 51 """
52 52
53 53 # Emitted when an 'execute_reply' is received from the kernel.
54 54 executed = QtCore.pyqtSignal(object)
55 55
56 56 #---------------------------------------------------------------------------
57 57 # 'QWidget' interface
58 58 #---------------------------------------------------------------------------
59 59
60 60 def __init__(self, kernel_manager, parent=None):
61 61 super(FrontendWidget, self).__init__(parent)
62 62
63 63 self._call_tip_widget = CallTipWidget(self)
64 64 self._compile = CommandCompiler()
65 65 self._completion_lexer = CompletionLexer(PythonLexer())
66 self._hidden = False
66 67 self._highlighter = FrontendHighlighter(self)
67 68 self._kernel_manager = None
68 69
69 70 self.continuation_prompt = '... '
70 71 self.kernel_manager = kernel_manager
71 72
72 73 self.document().contentsChange.connect(self._document_contents_change)
73 74
74 75 def focusOutEvent(self, event):
75 76 """ Reimplemented to hide calltips.
76 77 """
77 78 self._call_tip_widget.hide()
78 79 return super(FrontendWidget, self).focusOutEvent(event)
79 80
80 81 def keyPressEvent(self, event):
81 82 """ Reimplemented to hide calltips.
82 83 """
83 84 if event.key() == QtCore.Qt.Key_Escape:
84 85 self._call_tip_widget.hide()
85 86 return super(FrontendWidget, self).keyPressEvent(event)
86 87
87 88 #---------------------------------------------------------------------------
88 89 # 'ConsoleWidget' abstract interface
89 90 #---------------------------------------------------------------------------
90 91
91 92 def _execute(self, interactive):
92 93 """ Called to execute the input buffer. When triggered by an the enter
93 94 key press, 'interactive' is True; otherwise, it is False. Returns
94 95 whether the input buffer was completely processed and a new prompt
95 96 created.
96 97 """
97 98 return self.execute_source(self.input_buffer, interactive=interactive)
98 99
99 100 def _prompt_started_hook(self):
100 101 """ Called immediately after a new prompt is displayed.
101 102 """
102 103 self._highlighter.highlighting_on = True
103 104
104 105 def _prompt_finished_hook(self):
105 106 """ Called immediately after a prompt is finished, i.e. when some input
106 107 will be processed and a new prompt displayed.
107 108 """
108 109 self._highlighter.highlighting_on = False
109 110
110 111 def _tab_pressed(self):
111 112 """ Called when the tab key is pressed. Returns whether to continue
112 113 processing the event.
113 114 """
114 115 self._keep_cursor_in_buffer()
115 116 cursor = self.textCursor()
116 117 if not self._complete():
117 118 cursor.insertText(' ')
118 119 return False
119 120
120 121 #---------------------------------------------------------------------------
121 122 # 'FrontendWidget' interface
122 123 #---------------------------------------------------------------------------
123 124
124 125 def execute_source(self, source, hidden=False, interactive=False):
125 126 """ Execute a string containing Python code. If 'hidden', no output is
126 127 shown. Returns whether the source executed (i.e., returns True only
127 128 if no more input is necessary).
128 129 """
129 130 try:
130 131 code = self._compile(source, symbol='single')
131 132 except (OverflowError, SyntaxError, ValueError):
132 133 # Just let IPython deal with the syntax error.
133 134 code = Exception
134 135
135 136 # Only execute interactive multiline input if it ends with a blank line
136 137 lines = source.splitlines()
137 138 if interactive and len(lines) > 1 and lines[-1].strip() != '':
138 139 code = None
139 140
140 141 executed = code is not None
141 142 if executed:
142 143 self.kernel_manager.xreq_channel.execute(source)
144 self._hidden = hidden
143 145 else:
144 146 space = 0
145 147 for char in lines[-1]:
146 148 if char == '\t':
147 149 space += 4
148 150 elif char == ' ':
149 151 space += 1
150 152 else:
151 153 break
152 154 if source.endswith(':') or source.endswith(':\n'):
153 155 space += 4
154 156 self._show_continuation_prompt()
155 157 self.appendPlainText(' ' * space)
156 158
157 159 return executed
158 160
159 161 def execute_file(self, path, hidden=False):
160 162 """ Attempts to execute file with 'path'. If 'hidden', no output is
161 163 shown.
162 164 """
163 165 self.execute_source('run %s' % path, hidden=hidden)
164 166
165 167 def _get_kernel_manager(self):
166 168 """ Returns the current kernel manager.
167 169 """
168 170 return self._kernel_manager
169 171
170 172 def _set_kernel_manager(self, kernel_manager):
171 173 """ Sets a new kernel manager, configuring its channels as necessary.
172 174 """
173 175 # Disconnect the old kernel manager.
174 176 if self._kernel_manager is not None:
175 177 sub = self._kernel_manager.sub_channel
176 178 xreq = self._kernel_manager.xreq_channel
177 179 sub.message_received.disconnect(self._handle_sub)
178 180 xreq.execute_reply.disconnect(self._handle_execute_reply)
179 181 xreq.complete_reply.disconnect(self._handle_complete_reply)
180 182 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
181 183
182 184 # Connect the new kernel manager.
183 185 self._kernel_manager = kernel_manager
184 186 sub = kernel_manager.sub_channel
185 187 xreq = kernel_manager.xreq_channel
186 188 sub.message_received.connect(self._handle_sub)
187 189 xreq.execute_reply.connect(self._handle_execute_reply)
188 190 xreq.complete_reply.connect(self._handle_complete_reply)
189 191 xreq.object_info_reply.connect(self._handle_object_info_reply)
190 192
191 193 self._show_prompt('>>> ')
192 194
193 195 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
194 196
195 197 #---------------------------------------------------------------------------
196 198 # 'FrontendWidget' protected interface
197 199 #---------------------------------------------------------------------------
198 200
199 201 def _call_tip(self):
200 202 """ Shows a call tip, if appropriate, at the current cursor location.
201 203 """
202 204 # Decide if it makes sense to show a call tip
203 205 cursor = self.textCursor()
204 206 cursor.movePosition(QtGui.QTextCursor.Left)
205 207 document = self.document()
206 208 if document.characterAt(cursor.position()).toAscii() != '(':
207 209 return False
208 210 context = self._get_context(cursor)
209 211 if not context:
210 212 return False
211 213
212 214 # Send the metadata request to the kernel
213 215 name = '.'.join(context)
214 216 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
215 217 self._calltip_pos = self.textCursor().position()
216 218 return True
217 219
218 220 def _complete(self):
219 221 """ Performs completion at the current cursor location.
220 222 """
221 223 # Decide if it makes sense to do completion
222 224 context = self._get_context()
223 225 if not context:
224 226 return False
225 227
226 228 # Send the completion request to the kernel
227 229 text = '.'.join(context)
228 230 self._complete_id = self.kernel_manager.xreq_channel.complete(
229 231 text, self.input_buffer_cursor_line, self.input_buffer)
230 232 self._complete_pos = self.textCursor().position()
231 233 return True
232 234
233 235 def _get_context(self, cursor=None):
234 236 """ Gets the context at the current cursor location.
235 237 """
236 238 if cursor is None:
237 239 cursor = self.textCursor()
238 240 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
239 241 QtGui.QTextCursor.KeepAnchor)
240 242 text = unicode(cursor.selectedText())
241 243 return self._completion_lexer.get_context(text)
242 244
243 245 #------ Signal handlers ----------------------------------------------------
244 246
245 247 def _document_contents_change(self, position, removed, added):
246 248 """ Called whenever the document's content changes. Display a calltip
247 249 if appropriate.
248 250 """
249 251 # Calculate where the cursor should be *after* the change:
250 252 position += added
251 253
252 254 document = self.document()
253 255 if position == self.textCursor().position():
254 256 self._call_tip()
255 257
256 258 def _handle_sub(self, omsg):
257 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
258 if handler is not None:
259 handler(omsg)
259 if not self._hidden:
260 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
261 if handler is not None:
262 handler(omsg)
260 263
261 264 def _handle_pyout(self, omsg):
262 265 session = omsg['parent_header']['session']
263 266 if session == self.kernel_manager.session.session:
264 267 self.appendPlainText(omsg['content']['data'] + '\n')
265 268
266 269 def _handle_stream(self, omsg):
267 270 self.appendPlainText(omsg['content']['data'])
268 271
269 272 def _handle_execute_reply(self, rep):
270 273 # Make sure that all output from the SUB channel has been processed
271 274 # before writing a new prompt.
272 275 self.kernel_manager.sub_channel.flush()
273 276
274 277 content = rep['content']
275 278 status = content['status']
276 279 if status == 'error':
277 280 self.appendPlainText(content['traceback'][-1])
278 281 elif status == 'aborted':
279 282 text = "ERROR: ABORTED\n"
280 283 self.appendPlainText(text)
284 self._hidden = False
281 285 self._show_prompt('>>> ')
282 286 self.executed.emit(rep)
283 287
284 288 def _handle_complete_reply(self, rep):
285 289 cursor = self.textCursor()
286 290 if rep['parent_header']['msg_id'] == self._complete_id and \
287 291 cursor.position() == self._complete_pos:
288 292 text = '.'.join(self._get_context())
289 293 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
290 294 self._complete_with_items(cursor, rep['content']['matches'])
291 295
292 296 def _handle_object_info_reply(self, rep):
293 297 cursor = self.textCursor()
294 298 if rep['parent_header']['msg_id'] == self._calltip_id and \
295 299 cursor.position() == self._calltip_pos:
296 300 doc = rep['content']['docstring']
297 301 if doc:
298 302 self._call_tip_widget.show_tip(doc)
299 303
300 304
301 305 if __name__ == '__main__':
302 306 import sys
303 307 from IPython.frontend.qt.kernelmanager import QtKernelManager
304 308
305 309 # Create KernelManager
306 310 xreq_addr = ('127.0.0.1', 5575)
307 311 sub_addr = ('127.0.0.1', 5576)
308 312 rep_addr = ('127.0.0.1', 5577)
309 313 kernel_manager = QtKernelManager(xreq_addr, sub_addr, rep_addr)
310 314 kernel_manager.sub_channel.start()
311 315 kernel_manager.xreq_channel.start()
312 316
313 317 # Launch application
314 318 app = QtGui.QApplication(sys.argv)
315 319 widget = FrontendWidget(kernel_manager)
316 320 widget.setWindowTitle('Python')
317 321 widget.resize(640, 480)
318 322 widget.show()
319 323 sys.exit(app.exec_())
320 324
General Comments 0
You need to be logged in to leave comments. Login now