##// END OF EJS Templates
Fixed imports and removed references to ETS/EPD
epatters -
Show More
@@ -1,383 +1,380 b''
1 # Standard library imports
1 # Standard library imports
2 from codeop import CommandCompiler
2 from codeop import CommandCompiler
3 from threading import Thread
3 from threading import Thread
4 import time
4 import time
5 import types
5 import types
6
6
7 # System library imports
7 # System library imports
8 from IPython.zmq.session import Message, Session
8 from IPython.zmq.session import Message, Session
9 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
10 from PyQt4 import QtCore, QtGui
10 from PyQt4 import QtCore, QtGui
11 import zmq
11 import zmq
12
12
13 # ETS imports
14 from enthought.pyface.ui.qt4.code_editor.pygments_highlighter import \
15 PygmentsHighlighter
16
17 # Local imports
13 # Local imports
18 from call_tip_widget import CallTipWidget
14 from call_tip_widget import CallTipWidget
19 from completion_lexer import CompletionLexer
15 from completion_lexer import CompletionLexer
20 from console_widget import HistoryConsoleWidget
16 from console_widget import HistoryConsoleWidget
17 from pygments_highlighter import PygmentsHighlighter
21
18
22
19
23 class FrontendReplyThread(Thread, QtCore.QObject):
20 class FrontendReplyThread(Thread, QtCore.QObject):
24 """ A Thread that receives a reply from the kernel for the frontend.
21 """ A Thread that receives a reply from the kernel for the frontend.
25 """
22 """
26
23
27 finished = QtCore.pyqtSignal()
24 finished = QtCore.pyqtSignal()
28 output_received = QtCore.pyqtSignal(Message)
25 output_received = QtCore.pyqtSignal(Message)
29 reply_received = QtCore.pyqtSignal(Message)
26 reply_received = QtCore.pyqtSignal(Message)
30
27
31 def __init__(self, parent):
28 def __init__(self, parent):
32 """ Create a FrontendReplyThread for the specified frontend.
29 """ Create a FrontendReplyThread for the specified frontend.
33 """
30 """
34 assert isinstance(parent, FrontendWidget)
31 assert isinstance(parent, FrontendWidget)
35 QtCore.QObject.__init__(self, parent)
32 QtCore.QObject.__init__(self, parent)
36 Thread.__init__(self)
33 Thread.__init__(self)
37
34
38 self.sleep_time = 0.05
35 self.sleep_time = 0.05
39
36
40 def run(self):
37 def run(self):
41 """ The starting point for the thread.
38 """ The starting point for the thread.
42 """
39 """
43 frontend = self.parent()
40 frontend = self.parent()
44 while True:
41 while True:
45 rep = frontend._recv_reply()
42 rep = frontend._recv_reply()
46 if rep is not None:
43 if rep is not None:
47 self._recv_output()
44 self._recv_output()
48 self.reply_received.emit(rep)
45 self.reply_received.emit(rep)
49 break
46 break
50
47
51 self._recv_output()
48 self._recv_output()
52 time.sleep(self.sleep_time)
49 time.sleep(self.sleep_time)
53
50
54 self.finished.emit()
51 self.finished.emit()
55
52
56 def _recv_output(self):
53 def _recv_output(self):
57 """ Send any output to the frontend.
54 """ Send any output to the frontend.
58 """
55 """
59 frontend = self.parent()
56 frontend = self.parent()
60 omsgs = frontend._recv_output()
57 omsgs = frontend._recv_output()
61 for omsg in omsgs:
58 for omsg in omsgs:
62 self.output_received.emit(omsg)
59 self.output_received.emit(omsg)
63
60
64
61
65 class FrontendHighlighter(PygmentsHighlighter):
62 class FrontendHighlighter(PygmentsHighlighter):
66 """ A Python PygmentsHighlighter that can be turned on and off and which
63 """ A Python PygmentsHighlighter that can be turned on and off and which
67 knows about continuation prompts.
64 knows about continuation prompts.
68 """
65 """
69
66
70 def __init__(self, frontend):
67 def __init__(self, frontend):
71 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
68 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
72 self._current_offset = 0
69 self._current_offset = 0
73 self._frontend = frontend
70 self._frontend = frontend
74 self.highlighting_on = False
71 self.highlighting_on = False
75
72
76 def highlightBlock(self, qstring):
73 def highlightBlock(self, qstring):
77 """ Highlight a block of text. Reimplemented to highlight selectively.
74 """ Highlight a block of text. Reimplemented to highlight selectively.
78 """
75 """
79 if self.highlighting_on:
76 if self.highlighting_on:
80 for prompt in (self._frontend._prompt,
77 for prompt in (self._frontend._prompt,
81 self._frontend.continuation_prompt):
78 self._frontend.continuation_prompt):
82 if qstring.startsWith(prompt):
79 if qstring.startsWith(prompt):
83 qstring.remove(0, len(prompt))
80 qstring.remove(0, len(prompt))
84 self._current_offset = len(prompt)
81 self._current_offset = len(prompt)
85 break
82 break
86 PygmentsHighlighter.highlightBlock(self, qstring)
83 PygmentsHighlighter.highlightBlock(self, qstring)
87
84
88 def setFormat(self, start, count, format):
85 def setFormat(self, start, count, format):
89 """ Reimplemented to avoid highlighting continuation prompts.
86 """ Reimplemented to avoid highlighting continuation prompts.
90 """
87 """
91 start += self._current_offset
88 start += self._current_offset
92 PygmentsHighlighter.setFormat(self, start, count, format)
89 PygmentsHighlighter.setFormat(self, start, count, format)
93
90
94
91
95 class FrontendWidget(HistoryConsoleWidget):
92 class FrontendWidget(HistoryConsoleWidget):
96 """ A Qt frontend for an IPython kernel.
93 """ A Qt frontend for an IPython kernel.
97 """
94 """
98
95
99 # Emitted when an 'execute_reply' is received from the kernel.
96 # Emitted when an 'execute_reply' is received from the kernel.
100 executed = QtCore.pyqtSignal(Message)
97 executed = QtCore.pyqtSignal(Message)
101
98
102 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
103 # 'QWidget' interface
100 # 'QWidget' interface
104 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
105
102
106 def __init__(self, parent=None, session=None, request_socket=None,
103 def __init__(self, parent=None, session=None, request_socket=None,
107 sub_socket=None):
104 sub_socket=None):
108 super(FrontendWidget, self).__init__(parent)
105 super(FrontendWidget, self).__init__(parent)
109 self.continuation_prompt = '... '
106 self.continuation_prompt = '... '
110
107
111 self._call_tip_widget = CallTipWidget(self)
108 self._call_tip_widget = CallTipWidget(self)
112 self._compile = CommandCompiler()
109 self._compile = CommandCompiler()
113 self._completion_lexer = CompletionLexer(PythonLexer())
110 self._completion_lexer = CompletionLexer(PythonLexer())
114 self._highlighter = FrontendHighlighter(self)
111 self._highlighter = FrontendHighlighter(self)
115
112
116 self.session = Session() if session is None else session
113 self.session = Session() if session is None else session
117 self.request_socket = request_socket
114 self.request_socket = request_socket
118 self.sub_socket = sub_socket
115 self.sub_socket = sub_socket
119
116
120 self.document().contentsChange.connect(self._document_contents_change)
117 self.document().contentsChange.connect(self._document_contents_change)
121
118
122 self._kernel_connected() # XXX
119 self._kernel_connected() # XXX
123
120
124 def focusOutEvent(self, event):
121 def focusOutEvent(self, event):
125 """ Reimplemented to hide calltips.
122 """ Reimplemented to hide calltips.
126 """
123 """
127 self._call_tip_widget.hide()
124 self._call_tip_widget.hide()
128 return super(FrontendWidget, self).focusOutEvent(event)
125 return super(FrontendWidget, self).focusOutEvent(event)
129
126
130 def keyPressEvent(self, event):
127 def keyPressEvent(self, event):
131 """ Reimplemented to hide calltips.
128 """ Reimplemented to hide calltips.
132 """
129 """
133 if event.key() == QtCore.Qt.Key_Escape:
130 if event.key() == QtCore.Qt.Key_Escape:
134 self._call_tip_widget.hide()
131 self._call_tip_widget.hide()
135 return super(FrontendWidget, self).keyPressEvent(event)
132 return super(FrontendWidget, self).keyPressEvent(event)
136
133
137 #---------------------------------------------------------------------------
134 #---------------------------------------------------------------------------
138 # 'ConsoleWidget' abstract interface
135 # 'ConsoleWidget' abstract interface
139 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
140
137
141 def _execute(self, interactive):
138 def _execute(self, interactive):
142 """ Called to execute the input buffer. When triggered by an the enter
139 """ Called to execute the input buffer. When triggered by an the enter
143 key press, 'interactive' is True; otherwise, it is False. Returns
140 key press, 'interactive' is True; otherwise, it is False. Returns
144 whether the input buffer was completely processed and a new prompt
141 whether the input buffer was completely processed and a new prompt
145 created.
142 created.
146 """
143 """
147 return self.execute_source(self.input_buffer, interactive=interactive)
144 return self.execute_source(self.input_buffer, interactive=interactive)
148
145
149 def _prompt_started_hook(self):
146 def _prompt_started_hook(self):
150 """ Called immediately after a new prompt is displayed.
147 """ Called immediately after a new prompt is displayed.
151 """
148 """
152 self._highlighter.highlighting_on = True
149 self._highlighter.highlighting_on = True
153
150
154 def _prompt_finished_hook(self):
151 def _prompt_finished_hook(self):
155 """ Called immediately after a prompt is finished, i.e. when some input
152 """ Called immediately after a prompt is finished, i.e. when some input
156 will be processed and a new prompt displayed.
153 will be processed and a new prompt displayed.
157 """
154 """
158 self._highlighter.highlighting_on = False
155 self._highlighter.highlighting_on = False
159
156
160 def _tab_pressed(self):
157 def _tab_pressed(self):
161 """ Called when the tab key is pressed. Returns whether to continue
158 """ Called when the tab key is pressed. Returns whether to continue
162 processing the event.
159 processing the event.
163 """
160 """
164 self._keep_cursor_in_buffer()
161 self._keep_cursor_in_buffer()
165 cursor = self.textCursor()
162 cursor = self.textCursor()
166 if not self._complete():
163 if not self._complete():
167 cursor.insertText(' ')
164 cursor.insertText(' ')
168 return False
165 return False
169
166
170 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
171 # 'FrontendWidget' interface
168 # 'FrontendWidget' interface
172 #---------------------------------------------------------------------------
169 #---------------------------------------------------------------------------
173
170
174 def execute_source(self, source, hidden=False, interactive=False):
171 def execute_source(self, source, hidden=False, interactive=False):
175 """ Execute a string containing Python code. If 'hidden', no output is
172 """ Execute a string containing Python code. If 'hidden', no output is
176 shown. Returns whether the source executed (i.e., returns True only
173 shown. Returns whether the source executed (i.e., returns True only
177 if no more input is necessary).
174 if no more input is necessary).
178 """
175 """
179 try:
176 try:
180 code = self._compile(source, symbol='single')
177 code = self._compile(source, symbol='single')
181 except (OverflowError, SyntaxError, ValueError):
178 except (OverflowError, SyntaxError, ValueError):
182 # Just let IPython deal with the syntax error.
179 # Just let IPython deal with the syntax error.
183 code = Exception
180 code = Exception
184
181
185 # Only execute interactive multiline input if it ends with a blank line
182 # Only execute interactive multiline input if it ends with a blank line
186 lines = source.splitlines()
183 lines = source.splitlines()
187 if interactive and len(lines) > 1 and lines[-1].strip() != '':
184 if interactive and len(lines) > 1 and lines[-1].strip() != '':
188 code = None
185 code = None
189
186
190 executed = code is not None
187 executed = code is not None
191 if executed:
188 if executed:
192 msg = self.session.send(self.request_socket, 'execute_request',
189 msg = self.session.send(self.request_socket, 'execute_request',
193 dict(code=source))
190 dict(code=source))
194 thread = FrontendReplyThread(self)
191 thread = FrontendReplyThread(self)
195 if not hidden:
192 if not hidden:
196 thread.output_received.connect(self._handle_output)
193 thread.output_received.connect(self._handle_output)
197 thread.reply_received.connect(self._handle_reply)
194 thread.reply_received.connect(self._handle_reply)
198 thread.finished.connect(thread.deleteLater)
195 thread.finished.connect(thread.deleteLater)
199 thread.start()
196 thread.start()
200 else:
197 else:
201 space = 0
198 space = 0
202 for char in lines[-1]:
199 for char in lines[-1]:
203 if char == '\t':
200 if char == '\t':
204 space += 4
201 space += 4
205 elif char == ' ':
202 elif char == ' ':
206 space += 1
203 space += 1
207 else:
204 else:
208 break
205 break
209 if source.endswith(':') or source.endswith(':\n'):
206 if source.endswith(':') or source.endswith(':\n'):
210 space += 4
207 space += 4
211 self._show_continuation_prompt()
208 self._show_continuation_prompt()
212 self.appendPlainText(' ' * space)
209 self.appendPlainText(' ' * space)
213
210
214 return executed
211 return executed
215
212
216 def execute_file(self, path, hidden=False):
213 def execute_file(self, path, hidden=False):
217 """ Attempts to execute file with 'path'. If 'hidden', no output is
214 """ Attempts to execute file with 'path'. If 'hidden', no output is
218 shown.
215 shown.
219 """
216 """
220 self.execute_source('run %s' % path, hidden=hidden)
217 self.execute_source('run %s' % path, hidden=hidden)
221
218
222 #---------------------------------------------------------------------------
219 #---------------------------------------------------------------------------
223 # 'FrontendWidget' protected interface
220 # 'FrontendWidget' protected interface
224 #---------------------------------------------------------------------------
221 #---------------------------------------------------------------------------
225
222
226 def _call_tip(self):
223 def _call_tip(self):
227 """ Shows a call tip, if appropriate, at the current cursor location.
224 """ Shows a call tip, if appropriate, at the current cursor location.
228 """
225 """
229 # Decide if it makes sense to show a call tip
226 # Decide if it makes sense to show a call tip
230 cursor = self.textCursor()
227 cursor = self.textCursor()
231 cursor.movePosition(QtGui.QTextCursor.Left)
228 cursor.movePosition(QtGui.QTextCursor.Left)
232 document = self.document()
229 document = self.document()
233 if document.characterAt(cursor.position()).toAscii() != '(':
230 if document.characterAt(cursor.position()).toAscii() != '(':
234 return False
231 return False
235 context = self._get_context(cursor)
232 context = self._get_context(cursor)
236 if not context:
233 if not context:
237 return False
234 return False
238
235
239 # Send the metadata request to the kernel
236 # Send the metadata request to the kernel
240 text = '.'.join(context)
237 text = '.'.join(context)
241 msg = self.session.send(self.request_socket, 'metadata_request',
238 msg = self.session.send(self.request_socket, 'metadata_request',
242 dict(context=text))
239 dict(context=text))
243
240
244 # Give the kernel some time to respond
241 # Give the kernel some time to respond
245 rep = self._recv_reply_now('metadata_reply')
242 rep = self._recv_reply_now('metadata_reply')
246 doc = rep.content.docstring if rep else ''
243 doc = rep.content.docstring if rep else ''
247
244
248 # Show the call tip
245 # Show the call tip
249 if doc:
246 if doc:
250 self._call_tip_widget.show_tip(doc)
247 self._call_tip_widget.show_tip(doc)
251 return True
248 return True
252
249
253 def _complete(self):
250 def _complete(self):
254 """ Performs completion at the current cursor location.
251 """ Performs completion at the current cursor location.
255 """
252 """
256 # Decide if it makes sense to do completion
253 # Decide if it makes sense to do completion
257 context = self._get_context()
254 context = self._get_context()
258 if not context:
255 if not context:
259 return False
256 return False
260
257
261 # Send the completion request to the kernel
258 # Send the completion request to the kernel
262 text = '.'.join(context)
259 text = '.'.join(context)
263 line = self.input_buffer_cursor_line
260 line = self.input_buffer_cursor_line
264 msg = self.session.send(self.request_socket, 'complete_request',
261 msg = self.session.send(self.request_socket, 'complete_request',
265 dict(text=text, line=line))
262 dict(text=text, line=line))
266
263
267 # Give the kernel some time to respond
264 # Give the kernel some time to respond
268 rep = self._recv_reply_now('complete_reply')
265 rep = self._recv_reply_now('complete_reply')
269 matches = rep.content.matches if rep else []
266 matches = rep.content.matches if rep else []
270
267
271 # Show the completion at the correct location
268 # Show the completion at the correct location
272 cursor = self.textCursor()
269 cursor = self.textCursor()
273 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
270 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
274 self._complete_with_items(cursor, matches)
271 self._complete_with_items(cursor, matches)
275 return True
272 return True
276
273
277 def _kernel_connected(self):
274 def _kernel_connected(self):
278 """ Called when the frontend is connected to a kernel.
275 """ Called when the frontend is connected to a kernel.
279 """
276 """
280 self._show_prompt('>>> ')
277 self._show_prompt('>>> ')
281
278
282 def _get_context(self, cursor=None):
279 def _get_context(self, cursor=None):
283 """ Gets the context at the current cursor location.
280 """ Gets the context at the current cursor location.
284 """
281 """
285 if cursor is None:
282 if cursor is None:
286 cursor = self.textCursor()
283 cursor = self.textCursor()
287 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
284 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
288 QtGui.QTextCursor.KeepAnchor)
285 QtGui.QTextCursor.KeepAnchor)
289 text = unicode(cursor.selectedText())
286 text = unicode(cursor.selectedText())
290 return self._completion_lexer.get_context(text)
287 return self._completion_lexer.get_context(text)
291
288
292 #------ Signal handlers ----------------------------------------------------
289 #------ Signal handlers ----------------------------------------------------
293
290
294 def _document_contents_change(self, position, removed, added):
291 def _document_contents_change(self, position, removed, added):
295 """ Called whenever the document's content changes. Display a calltip
292 """ Called whenever the document's content changes. Display a calltip
296 if appropriate.
293 if appropriate.
297 """
294 """
298 # Calculate where the cursor should be *after* the change:
295 # Calculate where the cursor should be *after* the change:
299 position += added
296 position += added
300
297
301 document = self.document()
298 document = self.document()
302 if position == self.textCursor().position():
299 if position == self.textCursor().position():
303 self._call_tip()
300 self._call_tip()
304
301
305 def _handle_output(self, omsg):
302 def _handle_output(self, omsg):
306 handler = getattr(self, '_handle_%s' % omsg.msg_type, None)
303 handler = getattr(self, '_handle_%s' % omsg.msg_type, None)
307 if handler is not None:
304 if handler is not None:
308 handler(omsg)
305 handler(omsg)
309
306
310 def _handle_pyout(self, omsg):
307 def _handle_pyout(self, omsg):
311 if omsg.parent_header.session == self.session.session:
308 if omsg.parent_header.session == self.session.session:
312 self.appendPlainText(omsg.content.data + '\n')
309 self.appendPlainText(omsg.content.data + '\n')
313
310
314 def _handle_stream(self, omsg):
311 def _handle_stream(self, omsg):
315 self.appendPlainText(omsg.content.data)
312 self.appendPlainText(omsg.content.data)
316
313
317 def _handle_reply(self, rep):
314 def _handle_reply(self, rep):
318 if rep is not None:
315 if rep is not None:
319 if rep.msg_type == 'execute_reply':
316 if rep.msg_type == 'execute_reply':
320 if rep.content.status == 'error':
317 if rep.content.status == 'error':
321 self.appendPlainText(rep.content.traceback[-1])
318 self.appendPlainText(rep.content.traceback[-1])
322 elif rep.content.status == 'aborted':
319 elif rep.content.status == 'aborted':
323 text = "ERROR: ABORTED\n"
320 text = "ERROR: ABORTED\n"
324 ab = self.messages[rep.parent_header.msg_id].content
321 ab = self.messages[rep.parent_header.msg_id].content
325 if 'code' in ab:
322 if 'code' in ab:
326 text += ab.code
323 text += ab.code
327 else:
324 else:
328 text += ab
325 text += ab
329 self.appendPlainText(text)
326 self.appendPlainText(text)
330 self._show_prompt('>>> ')
327 self._show_prompt('>>> ')
331 self.executed.emit(rep)
328 self.executed.emit(rep)
332
329
333 #------ Communication methods ----------------------------------------------
330 #------ Communication methods ----------------------------------------------
334
331
335 def _recv_output(self):
332 def _recv_output(self):
336 omsgs = []
333 omsgs = []
337 while True:
334 while True:
338 omsg = self.session.recv(self.sub_socket)
335 omsg = self.session.recv(self.sub_socket)
339 if omsg is None:
336 if omsg is None:
340 break
337 break
341 else:
338 else:
342 omsgs.append(omsg)
339 omsgs.append(omsg)
343 return omsgs
340 return omsgs
344
341
345 def _recv_reply(self):
342 def _recv_reply(self):
346 return self.session.recv(self.request_socket)
343 return self.session.recv(self.request_socket)
347
344
348 def _recv_reply_now(self, msg_type):
345 def _recv_reply_now(self, msg_type):
349 for i in xrange(5):
346 for i in xrange(5):
350 rep = self._recv_reply()
347 rep = self._recv_reply()
351 if rep is not None and rep.msg_type == msg_type:
348 if rep is not None and rep.msg_type == msg_type:
352 return rep
349 return rep
353 time.sleep(0.1)
350 time.sleep(0.1)
354 return None
351 return None
355
352
356
353
357 if __name__ == '__main__':
354 if __name__ == '__main__':
358 import sys
355 import sys
359
356
360 # Defaults
357 # Defaults
361 ip = '127.0.0.1'
358 ip = '127.0.0.1'
362 port_base = 5555
359 port_base = 5555
363 connection = ('tcp://%s' % ip) + ':%i'
360 connection = ('tcp://%s' % ip) + ':%i'
364 req_conn = connection % port_base
361 req_conn = connection % port_base
365 sub_conn = connection % (port_base+1)
362 sub_conn = connection % (port_base+1)
366
363
367 # Create initial sockets
364 # Create initial sockets
368 c = zmq.Context()
365 c = zmq.Context()
369 request_socket = c.socket(zmq.XREQ)
366 request_socket = c.socket(zmq.XREQ)
370 request_socket.connect(req_conn)
367 request_socket.connect(req_conn)
371 sub_socket = c.socket(zmq.SUB)
368 sub_socket = c.socket(zmq.SUB)
372 sub_socket.connect(sub_conn)
369 sub_socket.connect(sub_conn)
373 sub_socket.setsockopt(zmq.SUBSCRIBE, '')
370 sub_socket.setsockopt(zmq.SUBSCRIBE, '')
374
371
375 # Launch application
372 # Launch application
376 app = QtGui.QApplication(sys.argv)
373 app = QtGui.QApplication(sys.argv)
377 widget = FrontendWidget(request_socket=request_socket,
374 widget = FrontendWidget(request_socket=request_socket,
378 sub_socket=sub_socket)
375 sub_socket=sub_socket)
379 widget.setWindowTitle('Python')
376 widget.setWindowTitle('Python')
380 widget.resize(640, 480)
377 widget.resize(640, 480)
381 widget.show()
378 widget.show()
382 sys.exit(app.exec_())
379 sys.exit(app.exec_())
383
380
@@ -1,221 +1,183 b''
1 #------------------------------------------------------------------------------
1 # System library imports.
2 # Copyright (c) 2010, Enthought Inc
3 # All rights reserved.
4 #
5 # This software is provided without warranty under the terms of the BSD license.
6
7 #
8 # Author: Enthought Inc
9 # Description: <Enthought pyface code editor>
10 #------------------------------------------------------------------------------
11
12 from PyQt4 import QtGui
2 from PyQt4 import QtGui
13
14 from pygments.lexer import RegexLexer, _TokenType, Text, Error
3 from pygments.lexer import RegexLexer, _TokenType, Text, Error
15 from pygments.lexers import CLexer, CppLexer, PythonLexer
4 from pygments.lexers import CLexer, CppLexer, PythonLexer
16 from pygments.styles.default import DefaultStyle
5 from pygments.styles.default import DefaultStyle
17 from pygments.token import Comment
6 from pygments.token import Comment
18
7
19
8
20 def get_tokens_unprocessed(self, text, stack=('root',)):
9 def get_tokens_unprocessed(self, text, stack=('root',)):
21 """ Split ``text`` into (tokentype, text) pairs.
10 """ Split ``text`` into (tokentype, text) pairs.
22
11
23 Monkeypatched to store the final stack on the object itself.
12 Monkeypatched to store the final stack on the object itself.
24 """
13 """
25 pos = 0
14 pos = 0
26 tokendefs = self._tokens
15 tokendefs = self._tokens
27 if hasattr(self, '_epd_state_stack'):
16 if hasattr(self, '_saved_state_stack'):
28 statestack = list(self._epd_state_stack)
17 statestack = list(self._saved_state_stack)
29 else:
18 else:
30 statestack = list(stack)
19 statestack = list(stack)
31 statetokens = tokendefs[statestack[-1]]
20 statetokens = tokendefs[statestack[-1]]
32 while 1:
21 while 1:
33 for rexmatch, action, new_state in statetokens:
22 for rexmatch, action, new_state in statetokens:
34 m = rexmatch(text, pos)
23 m = rexmatch(text, pos)
35 if m:
24 if m:
36 if type(action) is _TokenType:
25 if type(action) is _TokenType:
37 yield pos, action, m.group()
26 yield pos, action, m.group()
38 else:
27 else:
39 for item in action(self, m):
28 for item in action(self, m):
40 yield item
29 yield item
41 pos = m.end()
30 pos = m.end()
42 if new_state is not None:
31 if new_state is not None:
43 # state transition
32 # state transition
44 if isinstance(new_state, tuple):
33 if isinstance(new_state, tuple):
45 for state in new_state:
34 for state in new_state:
46 if state == '#pop':
35 if state == '#pop':
47 statestack.pop()
36 statestack.pop()
48 elif state == '#push':
37 elif state == '#push':
49 statestack.append(statestack[-1])
38 statestack.append(statestack[-1])
50 else:
39 else:
51 statestack.append(state)
40 statestack.append(state)
52 elif isinstance(new_state, int):
41 elif isinstance(new_state, int):
53 # pop
42 # pop
54 del statestack[new_state:]
43 del statestack[new_state:]
55 elif new_state == '#push':
44 elif new_state == '#push':
56 statestack.append(statestack[-1])
45 statestack.append(statestack[-1])
57 else:
46 else:
58 assert False, "wrong state def: %r" % new_state
47 assert False, "wrong state def: %r" % new_state
59 statetokens = tokendefs[statestack[-1]]
48 statetokens = tokendefs[statestack[-1]]
60 break
49 break
61 else:
50 else:
62 try:
51 try:
63 if text[pos] == '\n':
52 if text[pos] == '\n':
64 # at EOL, reset state to "root"
53 # at EOL, reset state to "root"
65 pos += 1
54 pos += 1
66 statestack = ['root']
55 statestack = ['root']
67 statetokens = tokendefs['root']
56 statetokens = tokendefs['root']
68 yield pos, Text, u'\n'
57 yield pos, Text, u'\n'
69 continue
58 continue
70 yield pos, Error, text[pos]
59 yield pos, Error, text[pos]
71 pos += 1
60 pos += 1
72 except IndexError:
61 except IndexError:
73 break
62 break
74 self._epd_state_stack = list(statestack)
63 self._saved_state_stack = list(statestack)
75
64
76 # Monkeypatch!
65 # Monkeypatch!
77 RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed
66 RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed
78
67
79
68
80 # Even with the above monkey patch to store state, multiline comments do not
81 # work since they are stateless (Pygments uses a single multiline regex for
82 # these comments, but Qt lexes by line). So we need to add a state for comments
83 # to the C and C++ lexers. This means that nested multiline comments will appear
84 # to be valid C/C++, but this is better than the alternative for now.
85
86 def replace_pattern(tokens, new_pattern):
87 """ Given a RegexLexer token dictionary 'tokens', replace all patterns that
88 match the token specified in 'new_pattern' with 'new_pattern'.
89 """
90 for state in tokens.values():
91 for index, pattern in enumerate(state):
92 if isinstance(pattern, tuple) and pattern[1] == new_pattern[1]:
93 state[index] = new_pattern
94
95 # More monkeypatching!
96 comment_start = (r'/\*', Comment.Multiline, 'comment')
97 comment_state = [ (r'[^*/]', Comment.Multiline),
98 (r'/\*', Comment.Multiline, '#push'),
99 (r'\*/', Comment.Multiline, '#pop'),
100 (r'[*/]', Comment.Multiline) ]
101 replace_pattern(CLexer.tokens, comment_start)
102 replace_pattern(CppLexer.tokens, comment_start)
103 CLexer.tokens['comment'] = comment_state
104 CppLexer.tokens['comment'] = comment_state
105
106
107 class BlockUserData(QtGui.QTextBlockUserData):
69 class BlockUserData(QtGui.QTextBlockUserData):
108 """ Storage for the user data associated with each line.
70 """ Storage for the user data associated with each line.
109 """
71 """
110
72
111 syntax_stack = ('root',)
73 syntax_stack = ('root',)
112
74
113 def __init__(self, **kwds):
75 def __init__(self, **kwds):
114 for key, value in kwds.iteritems():
76 for key, value in kwds.iteritems():
115 setattr(self, key, value)
77 setattr(self, key, value)
116 QtGui.QTextBlockUserData.__init__(self)
78 QtGui.QTextBlockUserData.__init__(self)
117
79
118 def __repr__(self):
80 def __repr__(self):
119 attrs = ['syntax_stack']
81 attrs = ['syntax_stack']
120 kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr))
82 kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr))
121 for attr in attrs ])
83 for attr in attrs ])
122 return 'BlockUserData(%s)' % kwds
84 return 'BlockUserData(%s)' % kwds
123
85
124
86
125 class PygmentsHighlighter(QtGui.QSyntaxHighlighter):
87 class PygmentsHighlighter(QtGui.QSyntaxHighlighter):
126 """ Syntax highlighter that uses Pygments for parsing. """
88 """ Syntax highlighter that uses Pygments for parsing. """
127
89
128 def __init__(self, parent, lexer=None):
90 def __init__(self, parent, lexer=None):
129 super(PygmentsHighlighter, self).__init__(parent)
91 super(PygmentsHighlighter, self).__init__(parent)
130
92
131 self._lexer = lexer if lexer else PythonLexer()
93 self._lexer = lexer if lexer else PythonLexer()
132 self._style = DefaultStyle
94 self._style = DefaultStyle
133 # Caches for formats and brushes.
95 # Caches for formats and brushes.
134 self._brushes = {}
96 self._brushes = {}
135 self._formats = {}
97 self._formats = {}
136
98
137 def highlightBlock(self, qstring):
99 def highlightBlock(self, qstring):
138 """ Highlight a block of text.
100 """ Highlight a block of text.
139 """
101 """
140 qstring = unicode(qstring)
102 qstring = unicode(qstring)
141 prev_data = self.previous_block_data()
103 prev_data = self.previous_block_data()
142
104
143 if prev_data is not None:
105 if prev_data is not None:
144 self._lexer._epd_state_stack = prev_data.syntax_stack
106 self._lexer._saved_state_stack = prev_data.syntax_stack
145 elif hasattr(self._lexer, '_epd_state_stack'):
107 elif hasattr(self._lexer, '_saved_state_stack'):
146 del self._lexer._epd_state_stack
108 del self._lexer._saved_state_stack
147
109
148 index = 0
110 index = 0
149 # Lex the text using Pygments
111 # Lex the text using Pygments
150 for token, text in self._lexer.get_tokens(qstring):
112 for token, text in self._lexer.get_tokens(qstring):
151 l = len(text)
113 l = len(text)
152 format = self._get_format(token)
114 format = self._get_format(token)
153 if format is not None:
115 if format is not None:
154 self.setFormat(index, l, format)
116 self.setFormat(index, l, format)
155 index += l
117 index += l
156
118
157 if hasattr(self._lexer, '_epd_state_stack'):
119 if hasattr(self._lexer, '_saved_state_stack'):
158 data = BlockUserData(syntax_stack=self._lexer._epd_state_stack)
120 data = BlockUserData(syntax_stack=self._lexer._saved_state_stack)
159 self.currentBlock().setUserData(data)
121 self.currentBlock().setUserData(data)
160 # Clean up for the next go-round.
122 # Clean up for the next go-round.
161 del self._lexer._epd_state_stack
123 del self._lexer._saved_state_stack
162
124
163 def previous_block_data(self):
125 def previous_block_data(self):
164 """ Convenience method for returning the previous block's user data.
126 """ Convenience method for returning the previous block's user data.
165 """
127 """
166 return self.currentBlock().previous().userData()
128 return self.currentBlock().previous().userData()
167
129
168 def _get_format(self, token):
130 def _get_format(self, token):
169 """ Returns a QTextCharFormat for token or None.
131 """ Returns a QTextCharFormat for token or None.
170 """
132 """
171 if token in self._formats:
133 if token in self._formats:
172 return self._formats[token]
134 return self._formats[token]
173 result = None
135 result = None
174 for key, value in self._style.style_for_token(token) .items():
136 for key, value in self._style.style_for_token(token) .items():
175 if value:
137 if value:
176 if result is None:
138 if result is None:
177 result = QtGui.QTextCharFormat()
139 result = QtGui.QTextCharFormat()
178 if key == 'color':
140 if key == 'color':
179 result.setForeground(self._get_brush(value))
141 result.setForeground(self._get_brush(value))
180 elif key == 'bgcolor':
142 elif key == 'bgcolor':
181 result.setBackground(self._get_brush(value))
143 result.setBackground(self._get_brush(value))
182 elif key == 'bold':
144 elif key == 'bold':
183 result.setFontWeight(QtGui.QFont.Bold)
145 result.setFontWeight(QtGui.QFont.Bold)
184 elif key == 'italic':
146 elif key == 'italic':
185 result.setFontItalic(True)
147 result.setFontItalic(True)
186 elif key == 'underline':
148 elif key == 'underline':
187 result.setUnderlineStyle(
149 result.setUnderlineStyle(
188 QtGui.QTextCharFormat.SingleUnderline)
150 QtGui.QTextCharFormat.SingleUnderline)
189 elif key == 'sans':
151 elif key == 'sans':
190 result.setFontStyleHint(QtGui.QFont.SansSerif)
152 result.setFontStyleHint(QtGui.QFont.SansSerif)
191 elif key == 'roman':
153 elif key == 'roman':
192 result.setFontStyleHint(QtGui.QFont.Times)
154 result.setFontStyleHint(QtGui.QFont.Times)
193 elif key == 'mono':
155 elif key == 'mono':
194 result.setFontStyleHint(QtGui.QFont.TypeWriter)
156 result.setFontStyleHint(QtGui.QFont.TypeWriter)
195 elif key == 'border':
157 elif key == 'border':
196 # Borders are normally used for errors. We can't do a border
158 # Borders are normally used for errors. We can't do a border
197 # so instead we do a wavy underline
159 # so instead we do a wavy underline
198 result.setUnderlineStyle(
160 result.setUnderlineStyle(
199 QtGui.QTextCharFormat.WaveUnderline)
161 QtGui.QTextCharFormat.WaveUnderline)
200 result.setUnderlineColor(self._get_color(value))
162 result.setUnderlineColor(self._get_color(value))
201 self._formats[token] = result
163 self._formats[token] = result
202 return result
164 return result
203
165
204 def _get_brush(self, color):
166 def _get_brush(self, color):
205 """ Returns a brush for the color.
167 """ Returns a brush for the color.
206 """
168 """
207 result = self._brushes.get(color)
169 result = self._brushes.get(color)
208 if result is None:
170 if result is None:
209 qcolor = self._get_color(color)
171 qcolor = self._get_color(color)
210 result = QtGui.QBrush(qcolor)
172 result = QtGui.QBrush(qcolor)
211 self._brushes[color] = result
173 self._brushes[color] = result
212
174
213 return result
175 return result
214
176
215 def _get_color(self, color):
177 def _get_color(self, color):
216 qcolor = QtGui.QColor()
178 qcolor = QtGui.QColor()
217 qcolor.setRgb(int(color[:2],base=16),
179 qcolor.setRgb(int(color[:2],base=16),
218 int(color[2:4], base=16),
180 int(color[2:4], base=16),
219 int(color[4:6], base=16))
181 int(color[4:6], base=16))
220 return qcolor
182 return qcolor
221
183
General Comments 0
You need to be logged in to leave comments. Login now