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