##// END OF EJS Templates
Updated FrontendWidget to use new 'replace' mode in BlockBreaker.
epatters -
Show More
@@ -1,277 +1,276 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.blockbreaker import BlockBreaker
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._prompt,
29 for prompt in (self._frontend._prompt,
30 self._frontend.continuation_prompt):
30 self._frontend.continuation_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 # 'QWidget' interface
52 # 'QWidget' interface
53 #---------------------------------------------------------------------------
53 #---------------------------------------------------------------------------
54
54
55 def __init__(self, kernel_manager, parent=None):
55 def __init__(self, kernel_manager, parent=None):
56 super(FrontendWidget, self).__init__(parent)
56 super(FrontendWidget, self).__init__(parent)
57
57
58 self._blockbreaker = BlockBreaker()
58 self._blockbreaker = BlockBreaker(input_mode='replace')
59 self._call_tip_widget = CallTipWidget(self)
59 self._call_tip_widget = CallTipWidget(self)
60 self._completion_lexer = CompletionLexer(PythonLexer())
60 self._completion_lexer = CompletionLexer(PythonLexer())
61 self._hidden = True
61 self._hidden = True
62 self._highlighter = FrontendHighlighter(self)
62 self._highlighter = FrontendHighlighter(self)
63 self._kernel_manager = None
63 self._kernel_manager = None
64
64
65 self.continuation_prompt = '... '
65 self.continuation_prompt = '... '
66 self.kernel_manager = kernel_manager
66 self.kernel_manager = kernel_manager
67
67
68 self.document().contentsChange.connect(self._document_contents_change)
68 self.document().contentsChange.connect(self._document_contents_change)
69
69
70 def focusOutEvent(self, event):
70 def focusOutEvent(self, event):
71 """ Reimplemented to hide calltips.
71 """ Reimplemented to hide calltips.
72 """
72 """
73 self._call_tip_widget.hide()
73 self._call_tip_widget.hide()
74 return super(FrontendWidget, self).focusOutEvent(event)
74 return super(FrontendWidget, self).focusOutEvent(event)
75
75
76 def keyPressEvent(self, event):
76 def keyPressEvent(self, event):
77 """ Reimplemented to hide calltips.
77 """ Reimplemented to hide calltips.
78 """
78 """
79 if event.key() == QtCore.Qt.Key_Escape:
79 if event.key() == QtCore.Qt.Key_Escape:
80 self._call_tip_widget.hide()
80 self._call_tip_widget.hide()
81 return super(FrontendWidget, self).keyPressEvent(event)
81 return super(FrontendWidget, self).keyPressEvent(event)
82
82
83 #---------------------------------------------------------------------------
83 #---------------------------------------------------------------------------
84 # 'ConsoleWidget' abstract interface
84 # 'ConsoleWidget' abstract interface
85 #---------------------------------------------------------------------------
85 #---------------------------------------------------------------------------
86
86
87 def _execute(self, interactive):
87 def _execute(self, interactive):
88 """ Called to execute the input buffer. When triggered by an the enter
88 """ Called to execute the input buffer. When triggered by an the enter
89 key press, 'interactive' is True; otherwise, it is False. Returns
89 key press, 'interactive' is True; otherwise, it is False. Returns
90 whether the input buffer was completely processed and a new prompt
90 whether the input buffer was completely processed and a new prompt
91 created.
91 created.
92 """
92 """
93 return self.execute_source(self.input_buffer, interactive=interactive)
93 return self.execute_source(self.input_buffer, interactive=interactive)
94
94
95 def _prompt_started_hook(self):
95 def _prompt_started_hook(self):
96 """ Called immediately after a new prompt is displayed.
96 """ Called immediately after a new prompt is displayed.
97 """
97 """
98 self._highlighter.highlighting_on = True
98 self._highlighter.highlighting_on = True
99
99
100 def _prompt_finished_hook(self):
100 def _prompt_finished_hook(self):
101 """ Called immediately after a prompt is finished, i.e. when some input
101 """ Called immediately after a prompt is finished, i.e. when some input
102 will be processed and a new prompt displayed.
102 will be processed and a new prompt displayed.
103 """
103 """
104 self._highlighter.highlighting_on = False
104 self._highlighter.highlighting_on = False
105
105
106 def _tab_pressed(self):
106 def _tab_pressed(self):
107 """ Called when the tab key is pressed. Returns whether to continue
107 """ Called when the tab key is pressed. Returns whether to continue
108 processing the event.
108 processing the event.
109 """
109 """
110 self._keep_cursor_in_buffer()
110 self._keep_cursor_in_buffer()
111 cursor = self.textCursor()
111 cursor = self.textCursor()
112 if not self._complete():
112 if not self._complete():
113 cursor.insertText(' ')
113 cursor.insertText(' ')
114 return False
114 return False
115
115
116 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
117 # 'FrontendWidget' interface
117 # 'FrontendWidget' interface
118 #---------------------------------------------------------------------------
118 #---------------------------------------------------------------------------
119
119
120 def execute_source(self, source, hidden=False, interactive=False):
120 def execute_source(self, source, hidden=False, interactive=False):
121 """ Execute a string containing Python code. If 'hidden', no output is
121 """ Execute a string containing Python code. If 'hidden', no output is
122 shown. Returns whether the source executed (i.e., returns True only
122 shown. Returns whether the source executed (i.e., returns True only
123 if no more input is necessary).
123 if no more input is necessary).
124 """
124 """
125 self._blockbreaker.reset()
126 self._blockbreaker.push(source)
125 self._blockbreaker.push(source)
127 executed = self._blockbreaker.interactive_block_ready()
126 executed = self._blockbreaker.interactive_block_ready()
128 if executed:
127 if executed:
129 self.kernel_manager.xreq_channel.execute(source)
128 self.kernel_manager.xreq_channel.execute(source)
130 self._hidden = hidden
129 self._hidden = hidden
131 else:
130 else:
132 self._show_continuation_prompt()
131 self._show_continuation_prompt()
133 self.appendPlainText(' ' * self._blockbreaker.indent_spaces)
132 self.appendPlainText(' ' * self._blockbreaker.indent_spaces)
134 return executed
133 return executed
135
134
136 def execute_file(self, path, hidden=False):
135 def execute_file(self, path, hidden=False):
137 """ Attempts to execute file with 'path'. If 'hidden', no output is
136 """ Attempts to execute file with 'path'. If 'hidden', no output is
138 shown.
137 shown.
139 """
138 """
140 self.execute_source('run %s' % path, hidden=hidden)
139 self.execute_source('run %s' % path, hidden=hidden)
141
140
142 def _get_kernel_manager(self):
141 def _get_kernel_manager(self):
143 """ Returns the current kernel manager.
142 """ Returns the current kernel manager.
144 """
143 """
145 return self._kernel_manager
144 return self._kernel_manager
146
145
147 def _set_kernel_manager(self, kernel_manager):
146 def _set_kernel_manager(self, kernel_manager):
148 """ Sets a new kernel manager, configuring its channels as necessary.
147 """ Sets a new kernel manager, configuring its channels as necessary.
149 """
148 """
150 # Disconnect the old kernel manager.
149 # Disconnect the old kernel manager.
151 if self._kernel_manager is not None:
150 if self._kernel_manager is not None:
152 sub = self._kernel_manager.sub_channel
151 sub = self._kernel_manager.sub_channel
153 xreq = self._kernel_manager.xreq_channel
152 xreq = self._kernel_manager.xreq_channel
154 sub.message_received.disconnect(self._handle_sub)
153 sub.message_received.disconnect(self._handle_sub)
155 xreq.execute_reply.disconnect(self._handle_execute_reply)
154 xreq.execute_reply.disconnect(self._handle_execute_reply)
156 xreq.complete_reply.disconnect(self._handle_complete_reply)
155 xreq.complete_reply.disconnect(self._handle_complete_reply)
157 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
156 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
158
157
159 # Connect the new kernel manager.
158 # Connect the new kernel manager.
160 self._kernel_manager = kernel_manager
159 self._kernel_manager = kernel_manager
161 sub = kernel_manager.sub_channel
160 sub = kernel_manager.sub_channel
162 xreq = kernel_manager.xreq_channel
161 xreq = kernel_manager.xreq_channel
163 sub.message_received.connect(self._handle_sub)
162 sub.message_received.connect(self._handle_sub)
164 xreq.execute_reply.connect(self._handle_execute_reply)
163 xreq.execute_reply.connect(self._handle_execute_reply)
165 xreq.complete_reply.connect(self._handle_complete_reply)
164 xreq.complete_reply.connect(self._handle_complete_reply)
166 xreq.object_info_reply.connect(self._handle_object_info_reply)
165 xreq.object_info_reply.connect(self._handle_object_info_reply)
167
166
168 self._show_prompt('>>> ')
167 self._show_prompt('>>> ')
169
168
170 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
169 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
171
170
172 #---------------------------------------------------------------------------
171 #---------------------------------------------------------------------------
173 # 'FrontendWidget' protected interface
172 # 'FrontendWidget' protected interface
174 #---------------------------------------------------------------------------
173 #---------------------------------------------------------------------------
175
174
176 def _call_tip(self):
175 def _call_tip(self):
177 """ Shows a call tip, if appropriate, at the current cursor location.
176 """ Shows a call tip, if appropriate, at the current cursor location.
178 """
177 """
179 # Decide if it makes sense to show a call tip
178 # Decide if it makes sense to show a call tip
180 cursor = self.textCursor()
179 cursor = self.textCursor()
181 cursor.movePosition(QtGui.QTextCursor.Left)
180 cursor.movePosition(QtGui.QTextCursor.Left)
182 document = self.document()
181 document = self.document()
183 if document.characterAt(cursor.position()).toAscii() != '(':
182 if document.characterAt(cursor.position()).toAscii() != '(':
184 return False
183 return False
185 context = self._get_context(cursor)
184 context = self._get_context(cursor)
186 if not context:
185 if not context:
187 return False
186 return False
188
187
189 # Send the metadata request to the kernel
188 # Send the metadata request to the kernel
190 name = '.'.join(context)
189 name = '.'.join(context)
191 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
190 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
192 self._calltip_pos = self.textCursor().position()
191 self._calltip_pos = self.textCursor().position()
193 return True
192 return True
194
193
195 def _complete(self):
194 def _complete(self):
196 """ Performs completion at the current cursor location.
195 """ Performs completion at the current cursor location.
197 """
196 """
198 # Decide if it makes sense to do completion
197 # Decide if it makes sense to do completion
199 context = self._get_context()
198 context = self._get_context()
200 if not context:
199 if not context:
201 return False
200 return False
202
201
203 # Send the completion request to the kernel
202 # Send the completion request to the kernel
204 text = '.'.join(context)
203 text = '.'.join(context)
205 self._complete_id = self.kernel_manager.xreq_channel.complete(
204 self._complete_id = self.kernel_manager.xreq_channel.complete(
206 text, self.input_buffer_cursor_line, self.input_buffer)
205 text, self.input_buffer_cursor_line, self.input_buffer)
207 self._complete_pos = self.textCursor().position()
206 self._complete_pos = self.textCursor().position()
208 return True
207 return True
209
208
210 def _get_context(self, cursor=None):
209 def _get_context(self, cursor=None):
211 """ Gets the context at the current cursor location.
210 """ Gets the context at the current cursor location.
212 """
211 """
213 if cursor is None:
212 if cursor is None:
214 cursor = self.textCursor()
213 cursor = self.textCursor()
215 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
214 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
216 QtGui.QTextCursor.KeepAnchor)
215 QtGui.QTextCursor.KeepAnchor)
217 text = unicode(cursor.selectedText())
216 text = unicode(cursor.selectedText())
218 return self._completion_lexer.get_context(text)
217 return self._completion_lexer.get_context(text)
219
218
220 #------ Signal handlers ----------------------------------------------------
219 #------ Signal handlers ----------------------------------------------------
221
220
222 def _document_contents_change(self, position, removed, added):
221 def _document_contents_change(self, position, removed, added):
223 """ Called whenever the document's content changes. Display a calltip
222 """ Called whenever the document's content changes. Display a calltip
224 if appropriate.
223 if appropriate.
225 """
224 """
226 # Calculate where the cursor should be *after* the change:
225 # Calculate where the cursor should be *after* the change:
227 position += added
226 position += added
228
227
229 document = self.document()
228 document = self.document()
230 if position == self.textCursor().position():
229 if position == self.textCursor().position():
231 self._call_tip()
230 self._call_tip()
232
231
233 def _handle_sub(self, omsg):
232 def _handle_sub(self, omsg):
234 if not self._hidden:
233 if not self._hidden:
235 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
234 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
236 if handler is not None:
235 if handler is not None:
237 handler(omsg)
236 handler(omsg)
238
237
239 def _handle_pyout(self, omsg):
238 def _handle_pyout(self, omsg):
240 session = omsg['parent_header']['session']
239 session = omsg['parent_header']['session']
241 if session == self.kernel_manager.session.session:
240 if session == self.kernel_manager.session.session:
242 self.appendPlainText(omsg['content']['data'] + '\n')
241 self.appendPlainText(omsg['content']['data'] + '\n')
243
242
244 def _handle_stream(self, omsg):
243 def _handle_stream(self, omsg):
245 self.appendPlainText(omsg['content']['data'])
244 self.appendPlainText(omsg['content']['data'])
246
245
247 def _handle_execute_reply(self, rep):
246 def _handle_execute_reply(self, rep):
248 # Make sure that all output from the SUB channel has been processed
247 # Make sure that all output from the SUB channel has been processed
249 # before writing a new prompt.
248 # before writing a new prompt.
250 self.kernel_manager.sub_channel.flush()
249 self.kernel_manager.sub_channel.flush()
251
250
252 content = rep['content']
251 content = rep['content']
253 status = content['status']
252 status = content['status']
254 if status == 'error':
253 if status == 'error':
255 self.appendPlainText(content['traceback'][-1])
254 self.appendPlainText(content['traceback'][-1])
256 elif status == 'aborted':
255 elif status == 'aborted':
257 text = "ERROR: ABORTED\n"
256 text = "ERROR: ABORTED\n"
258 self.appendPlainText(text)
257 self.appendPlainText(text)
259 self._hidden = True
258 self._hidden = True
260 self._show_prompt('>>> ')
259 self._show_prompt('>>> ')
261 self.executed.emit(rep)
260 self.executed.emit(rep)
262
261
263 def _handle_complete_reply(self, rep):
262 def _handle_complete_reply(self, rep):
264 cursor = self.textCursor()
263 cursor = self.textCursor()
265 if rep['parent_header']['msg_id'] == self._complete_id and \
264 if rep['parent_header']['msg_id'] == self._complete_id and \
266 cursor.position() == self._complete_pos:
265 cursor.position() == self._complete_pos:
267 text = '.'.join(self._get_context())
266 text = '.'.join(self._get_context())
268 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
267 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
269 self._complete_with_items(cursor, rep['content']['matches'])
268 self._complete_with_items(cursor, rep['content']['matches'])
270
269
271 def _handle_object_info_reply(self, rep):
270 def _handle_object_info_reply(self, rep):
272 cursor = self.textCursor()
271 cursor = self.textCursor()
273 if rep['parent_header']['msg_id'] == self._calltip_id and \
272 if rep['parent_header']['msg_id'] == self._calltip_id and \
274 cursor.position() == self._calltip_pos:
273 cursor.position() == self._calltip_pos:
275 doc = rep['content']['docstring']
274 doc = rep['content']['docstring']
276 if doc:
275 if doc:
277 self._call_tip_widget.show_tip(doc)
276 self._call_tip_widget.show_tip(doc)
General Comments 0
You need to be logged in to leave comments. Login now