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