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