##// END OF EJS Templates
First cut at a generic bracket matcher for Q[Plain]TextEdits.
epatters -
Show More
@@ -0,0 +1,101 b''
1 """ Provides bracket matching for Q[Plain]TextEdit widgets.
2 """
3
4 # System library imports
5 from PyQt4 import QtCore, QtGui
6
7
8 class BracketMatcher(QtCore.QObject):
9 """ Matches square brackets, braces, and parentheses based on cursor
10 position.
11 """
12
13 # Protected class variables.
14 _opening_map = { '(':')', '{':'}', '[':']' }
15 _closing_map = { ')':'(', '}':'{', ']':'[' }
16
17 #--------------------------------------------------------------------------
18 # 'QObject' interface
19 #--------------------------------------------------------------------------
20
21 def __init__(self, parent, multiline=True):
22 """ Create a call tip manager that is attached to the specified Qt
23 text edit widget.
24 """
25 assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
26 QtCore.QObject.__init__(self, parent)
27
28 # The format to apply to matching brackets.
29 self.format = QtGui.QTextCharFormat()
30 self.format.setBackground(QtGui.QColor('silver'))
31
32 parent.cursorPositionChanged.connect(self._cursor_position_changed)
33
34 #--------------------------------------------------------------------------
35 # Protected interface
36 #--------------------------------------------------------------------------
37
38 def _find_match(self, position):
39 """ Given a valid position in the text document, try to find the
40 position of the matching bracket. Returns -1 if unsuccessful.
41 """
42 # Decide what character to search for and what direction to search in.
43 document = self.parent().document()
44 qchar = document.characterAt(position)
45 start_char = qchar.toAscii()
46 search_char = self._opening_map.get(start_char)
47 if search_char:
48 increment = 1
49 else:
50 search_char = self._closing_map.get(start_char)
51 if search_char:
52 increment = -1
53 else:
54 return -1
55
56 # Search for the character.
57 depth = 0
58 while position >= 0 and position < document.characterCount():
59 char = qchar.toAscii()
60 if char == start_char:
61 depth += 1
62 elif char == search_char:
63 depth -= 1
64 if depth == 0:
65 break
66 position += increment
67 qchar = document.characterAt(position)
68 else:
69 position = -1
70 return position
71
72 def _selection_for_character(self, position):
73 """ Convenience method for selecting a character.
74 """
75 selection = QtGui.QTextEdit.ExtraSelection()
76 cursor = self.parent().textCursor()
77 cursor.setPosition(position)
78 cursor.movePosition(QtGui.QTextCursor.NextCharacter,
79 QtGui.QTextCursor.KeepAnchor)
80 selection.cursor = cursor
81 selection.format = self.format
82 return selection
83
84 #------ Signal handlers ----------------------------------------------------
85
86 def _cursor_position_changed(self):
87 """ Updates the document formatting based on the new cursor position.
88 """
89 # Clear out the old formatting.
90 text_edit = self.parent()
91 text_edit.setExtraSelections([])
92
93 # Attempt to match a bracket for the new cursor position.
94 cursor = text_edit.textCursor()
95 if not cursor.hasSelection():
96 position = cursor.position() - 1
97 match_position = self._find_match(position)
98 if match_position != -1:
99 extra_selections = [ self._selection_for_character(pos)
100 for pos in (position, match_position) ]
101 text_edit.setExtraSelections(extra_selections)
@@ -1,419 +1,420 b''
1 1 # Standard library imports
2 2 import signal
3 3 import sys
4 4
5 5 # System library imports
6 6 from pygments.lexers import PythonLexer
7 7 from PyQt4 import QtCore, QtGui
8 8
9 9 # Local imports
10 10 from IPython.core.inputsplitter import InputSplitter
11 11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.utils.traitlets import Bool, Type
12 from IPython.utils.traitlets import Bool
13 from bracket_matcher import BracketMatcher
13 14 from call_tip_widget import CallTipWidget
14 15 from completion_lexer import CompletionLexer
15 16 from console_widget import HistoryConsoleWidget
16 17 from pygments_highlighter import PygmentsHighlighter
17 18
18 19
19 20 class FrontendHighlighter(PygmentsHighlighter):
20 21 """ A PygmentsHighlighter that can be turned on and off and that ignores
21 22 prompts.
22 23 """
23 24
24 25 def __init__(self, frontend):
25 26 super(FrontendHighlighter, self).__init__(frontend._control.document())
26 27 self._current_offset = 0
27 28 self._frontend = frontend
28 29 self.highlighting_on = False
29 30
30 31 def highlightBlock(self, qstring):
31 32 """ Highlight a block of text. Reimplemented to highlight selectively.
32 33 """
33 34 if not self.highlighting_on:
34 35 return
35 36
36 37 # The input to this function is unicode string that may contain
37 38 # paragraph break characters, non-breaking spaces, etc. Here we acquire
38 39 # the string as plain text so we can compare it.
39 40 current_block = self.currentBlock()
40 41 string = self._frontend._get_block_plain_text(current_block)
41 42
42 43 # Decide whether to check for the regular or continuation prompt.
43 44 if current_block.contains(self._frontend._prompt_pos):
44 45 prompt = self._frontend._prompt
45 46 else:
46 47 prompt = self._frontend._continuation_prompt
47 48
48 49 # Don't highlight the part of the string that contains the prompt.
49 50 if string.startswith(prompt):
50 51 self._current_offset = len(prompt)
51 52 qstring.remove(0, len(prompt))
52 53 else:
53 54 self._current_offset = 0
54 55
55 56 PygmentsHighlighter.highlightBlock(self, qstring)
56 57
57 58 def rehighlightBlock(self, block):
58 59 """ Reimplemented to temporarily enable highlighting if disabled.
59 60 """
60 61 old = self.highlighting_on
61 62 self.highlighting_on = True
62 63 super(FrontendHighlighter, self).rehighlightBlock(block)
63 64 self.highlighting_on = old
64 65
65 66 def setFormat(self, start, count, format):
66 67 """ Reimplemented to highlight selectively.
67 68 """
68 69 start += self._current_offset
69 70 PygmentsHighlighter.setFormat(self, start, count, format)
70 71
71 72
72 73 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
73 74 """ A Qt frontend for a generic Python kernel.
74 75 """
75 76
76 77 # An option and corresponding signal for overriding the default kernel
77 78 # interrupt behavior.
78 79 custom_interrupt = Bool(False)
79 80 custom_interrupt_requested = QtCore.pyqtSignal()
80 81
81 82 # An option and corresponding signal for overriding the default kernel
82 83 # restart behavior.
83 84 custom_restart = Bool(False)
84 85 custom_restart_requested = QtCore.pyqtSignal()
85 86
86 87 # Emitted when an 'execute_reply' has been received from the kernel and
87 88 # processed by the FrontendWidget.
88 89 executed = QtCore.pyqtSignal(object)
89 90
90 91 # Protected class variables.
91 _highlighter_class = Type(FrontendHighlighter)
92 _input_splitter_class = Type(InputSplitter)
92 _input_splitter_class = InputSplitter
93 93
94 94 #---------------------------------------------------------------------------
95 95 # 'object' interface
96 96 #---------------------------------------------------------------------------
97 97
98 98 def __init__(self, *args, **kw):
99 99 super(FrontendWidget, self).__init__(*args, **kw)
100 100
101 101 # FrontendWidget protected variables.
102 self._bracket_matcher = BracketMatcher(self._control)
102 103 self._call_tip_widget = CallTipWidget(self._control)
103 104 self._completion_lexer = CompletionLexer(PythonLexer())
104 105 self._hidden = False
105 self._highlighter = self._highlighter_class(self)
106 self._highlighter = FrontendHighlighter(self)
106 107 self._input_splitter = self._input_splitter_class(input_mode='block')
107 108 self._kernel_manager = None
108 109
109 110 # Configure the ConsoleWidget.
110 111 self.tab_width = 4
111 112 self._set_continuation_prompt('... ')
112 113
113 114 # Connect signal handlers.
114 115 document = self._control.document()
115 116 document.contentsChange.connect(self._document_contents_change)
116 117
117 118 #---------------------------------------------------------------------------
118 119 # 'ConsoleWidget' abstract interface
119 120 #---------------------------------------------------------------------------
120 121
121 122 def _is_complete(self, source, interactive):
122 123 """ Returns whether 'source' can be completely processed and a new
123 124 prompt created. When triggered by an Enter/Return key press,
124 125 'interactive' is True; otherwise, it is False.
125 126 """
126 127 complete = self._input_splitter.push(source.expandtabs(4))
127 128 if interactive:
128 129 complete = not self._input_splitter.push_accepts_more()
129 130 return complete
130 131
131 132 def _execute(self, source, hidden):
132 133 """ Execute 'source'. If 'hidden', do not show any output.
133 134 """
134 135 self.kernel_manager.xreq_channel.execute(source, hidden)
135 136 self._hidden = hidden
136 137
137 138 def _prompt_started_hook(self):
138 139 """ Called immediately after a new prompt is displayed.
139 140 """
140 141 if not self._reading:
141 142 self._highlighter.highlighting_on = True
142 143
143 144 def _prompt_finished_hook(self):
144 145 """ Called immediately after a prompt is finished, i.e. when some input
145 146 will be processed and a new prompt displayed.
146 147 """
147 148 if not self._reading:
148 149 self._highlighter.highlighting_on = False
149 150
150 151 def _tab_pressed(self):
151 152 """ Called when the tab key is pressed. Returns whether to continue
152 153 processing the event.
153 154 """
154 155 # Perform tab completion if:
155 156 # 1) The cursor is in the input buffer.
156 157 # 2) There is a non-whitespace character before the cursor.
157 158 text = self._get_input_buffer_cursor_line()
158 159 if text is None:
159 160 return False
160 161 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
161 162 if complete:
162 163 self._complete()
163 164 return not complete
164 165
165 166 #---------------------------------------------------------------------------
166 167 # 'ConsoleWidget' protected interface
167 168 #---------------------------------------------------------------------------
168 169
169 170 def _event_filter_console_keypress(self, event):
170 171 """ Reimplemented to allow execution interruption.
171 172 """
172 173 key = event.key()
173 174 if self._executing and self._control_key_down(event.modifiers()):
174 175 if key == QtCore.Qt.Key_C:
175 176 self._kernel_interrupt()
176 177 return True
177 178 elif key == QtCore.Qt.Key_Period:
178 179 self._kernel_restart()
179 180 return True
180 181 return super(FrontendWidget, self)._event_filter_console_keypress(event)
181 182
182 183 def _show_continuation_prompt(self):
183 184 """ Reimplemented for auto-indentation.
184 185 """
185 186 super(FrontendWidget, self)._show_continuation_prompt()
186 187 spaces = self._input_splitter.indent_spaces
187 188 self._append_plain_text('\t' * (spaces / self.tab_width))
188 189 self._append_plain_text(' ' * (spaces % self.tab_width))
189 190
190 191 #---------------------------------------------------------------------------
191 192 # 'BaseFrontendMixin' abstract interface
192 193 #---------------------------------------------------------------------------
193 194
194 195 def _handle_complete_reply(self, rep):
195 196 """ Handle replies for tab completion.
196 197 """
197 198 cursor = self._get_cursor()
198 199 if rep['parent_header']['msg_id'] == self._complete_id and \
199 200 cursor.position() == self._complete_pos:
200 201 text = '.'.join(self._get_context())
201 202 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
202 203 self._complete_with_items(cursor, rep['content']['matches'])
203 204
204 205 def _handle_execute_reply(self, msg):
205 206 """ Handles replies for code execution.
206 207 """
207 208 if not self._hidden:
208 209 # Make sure that all output from the SUB channel has been processed
209 210 # before writing a new prompt.
210 211 self.kernel_manager.sub_channel.flush()
211 212
212 213 content = msg['content']
213 214 status = content['status']
214 215 if status == 'ok':
215 216 self._process_execute_ok(msg)
216 217 elif status == 'error':
217 218 self._process_execute_error(msg)
218 219 elif status == 'abort':
219 220 self._process_execute_abort(msg)
220 221
221 222 self._show_interpreter_prompt_for_reply(msg)
222 223 self.executed.emit(msg)
223 224
224 225 def _handle_input_request(self, msg):
225 226 """ Handle requests for raw_input.
226 227 """
227 228 if self._hidden:
228 229 raise RuntimeError('Request for raw input during hidden execution.')
229 230
230 231 # Make sure that all output from the SUB channel has been processed
231 232 # before entering readline mode.
232 233 self.kernel_manager.sub_channel.flush()
233 234
234 235 def callback(line):
235 236 self.kernel_manager.rep_channel.input(line)
236 237 self._readline(msg['content']['prompt'], callback=callback)
237 238
238 239 def _handle_object_info_reply(self, rep):
239 240 """ Handle replies for call tips.
240 241 """
241 242 cursor = self._get_cursor()
242 243 if rep['parent_header']['msg_id'] == self._call_tip_id and \
243 244 cursor.position() == self._call_tip_pos:
244 245 doc = rep['content']['docstring']
245 246 if doc:
246 247 self._call_tip_widget.show_docstring(doc)
247 248
248 249 def _handle_pyout(self, msg):
249 250 """ Handle display hook output.
250 251 """
251 252 if not self._hidden and self._is_from_this_session(msg):
252 253 self._append_plain_text(msg['content']['data'] + '\n')
253 254
254 255 def _handle_stream(self, msg):
255 256 """ Handle stdout, stderr, and stdin.
256 257 """
257 258 if not self._hidden and self._is_from_this_session(msg):
258 259 self._append_plain_text(msg['content']['data'])
259 260 self._control.moveCursor(QtGui.QTextCursor.End)
260 261
261 262 def _started_channels(self):
262 263 """ Called when the KernelManager channels have started listening or
263 264 when the frontend is assigned an already listening KernelManager.
264 265 """
265 266 self._control.clear()
266 267 self._append_plain_text(self._get_banner())
267 268 self._show_interpreter_prompt()
268 269
269 270 def _stopped_channels(self):
270 271 """ Called when the KernelManager channels have stopped listening or
271 272 when a listening KernelManager is removed from the frontend.
272 273 """
273 274 self._executing = self._reading = False
274 275 self._highlighter.highlighting_on = False
275 276
276 277 #---------------------------------------------------------------------------
277 278 # 'FrontendWidget' interface
278 279 #---------------------------------------------------------------------------
279 280
280 281 def execute_file(self, path, hidden=False):
281 282 """ Attempts to execute file with 'path'. If 'hidden', no output is
282 283 shown.
283 284 """
284 285 self.execute('execfile("%s")' % path, hidden=hidden)
285 286
286 287 #---------------------------------------------------------------------------
287 288 # 'FrontendWidget' protected interface
288 289 #---------------------------------------------------------------------------
289 290
290 291 def _call_tip(self):
291 292 """ Shows a call tip, if appropriate, at the current cursor location.
292 293 """
293 294 # Decide if it makes sense to show a call tip
294 295 cursor = self._get_cursor()
295 296 cursor.movePosition(QtGui.QTextCursor.Left)
296 297 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
297 298 return False
298 299 context = self._get_context(cursor)
299 300 if not context:
300 301 return False
301 302
302 303 # Send the metadata request to the kernel
303 304 name = '.'.join(context)
304 305 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
305 306 self._call_tip_pos = self._get_cursor().position()
306 307 return True
307 308
308 309 def _complete(self):
309 310 """ Performs completion at the current cursor location.
310 311 """
311 312 context = self._get_context()
312 313 if context:
313 314 # Send the completion request to the kernel
314 315 self._complete_id = self.kernel_manager.xreq_channel.complete(
315 316 '.'.join(context), # text
316 317 self._get_input_buffer_cursor_line(), # line
317 318 self._get_input_buffer_cursor_column(), # cursor_pos
318 319 self.input_buffer) # block
319 320 self._complete_pos = self._get_cursor().position()
320 321
321 322 def _get_banner(self):
322 323 """ Gets a banner to display at the beginning of a session.
323 324 """
324 325 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
325 326 '"license" for more information.'
326 327 return banner % (sys.version, sys.platform)
327 328
328 329 def _get_context(self, cursor=None):
329 330 """ Gets the context for the specified cursor (or the current cursor
330 331 if none is specified).
331 332 """
332 333 if cursor is None:
333 334 cursor = self._get_cursor()
334 335 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
335 336 QtGui.QTextCursor.KeepAnchor)
336 337 text = str(cursor.selection().toPlainText())
337 338 return self._completion_lexer.get_context(text)
338 339
339 340 def _kernel_interrupt(self):
340 341 """ Attempts to interrupt the running kernel.
341 342 """
342 343 if self.custom_interrupt:
343 344 self.custom_interrupt_requested.emit()
344 345 elif self.kernel_manager.has_kernel:
345 346 self.kernel_manager.signal_kernel(signal.SIGINT)
346 347 else:
347 348 self._append_plain_text('Kernel process is either remote or '
348 349 'unspecified. Cannot interrupt.\n')
349 350
350 351 def _kernel_restart(self):
351 352 """ Attempts to restart the running kernel.
352 353 """
353 354 if self.custom_restart:
354 355 self.custom_restart_requested.emit()
355 356 elif self.kernel_manager.has_kernel:
356 357 try:
357 358 self.kernel_manager.restart_kernel()
358 359 except RuntimeError:
359 360 message = 'Kernel started externally. Cannot restart.\n'
360 361 self._append_plain_text(message)
361 362 else:
362 363 self._stopped_channels()
363 364 self._append_plain_text('Kernel restarting...\n')
364 365 self._show_interpreter_prompt()
365 366 else:
366 367 self._append_plain_text('Kernel process is either remote or '
367 368 'unspecified. Cannot restart.\n')
368 369
369 370 def _process_execute_abort(self, msg):
370 371 """ Process a reply for an aborted execution request.
371 372 """
372 373 self._append_plain_text("ERROR: execution aborted\n")
373 374
374 375 def _process_execute_error(self, msg):
375 376 """ Process a reply for an execution request that resulted in an error.
376 377 """
377 378 content = msg['content']
378 379 traceback = ''.join(content['traceback'])
379 380 self._append_plain_text(traceback)
380 381
381 382 def _process_execute_ok(self, msg):
382 383 """ Process a reply for a successful execution equest.
383 384 """
384 385 payload = msg['content']['payload']
385 386 for item in payload:
386 387 if not self._process_execute_payload(item):
387 388 warning = 'Received unknown payload of type %s\n'
388 389 self._append_plain_text(warning % repr(item['source']))
389 390
390 391 def _process_execute_payload(self, item):
391 392 """ Process a single payload item from the list of payload items in an
392 393 execution reply. Returns whether the payload was handled.
393 394 """
394 395 # The basic FrontendWidget doesn't handle payloads, as they are a
395 396 # mechanism for going beyond the standard Python interpreter model.
396 397 return False
397 398
398 399 def _show_interpreter_prompt(self):
399 400 """ Shows a prompt for the interpreter.
400 401 """
401 402 self._show_prompt('>>> ')
402 403
403 404 def _show_interpreter_prompt_for_reply(self, msg):
404 405 """ Shows a prompt for the interpreter given an 'execute_reply' message.
405 406 """
406 407 self._show_interpreter_prompt()
407 408
408 409 #------ Signal handlers ----------------------------------------------------
409 410
410 411 def _document_contents_change(self, position, removed, added):
411 412 """ Called whenever the document's content changes. Display a call tip
412 413 if appropriate.
413 414 """
414 415 # Calculate where the cursor should be *after* the change:
415 416 position += added
416 417
417 418 document = self._control.document()
418 419 if position == self._get_cursor().position():
419 420 self._call_tip()
@@ -1,230 +1,226 b''
1 1 # System library imports.
2 2 from PyQt4 import QtGui
3 3 from pygments.formatters.html import HtmlFormatter
4 4 from pygments.lexer import RegexLexer, _TokenType, Text, Error
5 5 from pygments.lexers import PythonLexer
6 6 from pygments.styles import get_style_by_name
7 7
8 8
9 9 def get_tokens_unprocessed(self, text, stack=('root',)):
10 10 """ Split ``text`` into (tokentype, text) pairs.
11 11
12 12 Monkeypatched to store the final stack on the object itself.
13 13 """
14 14 pos = 0
15 15 tokendefs = self._tokens
16 16 if hasattr(self, '_saved_state_stack'):
17 17 statestack = list(self._saved_state_stack)
18 18 else:
19 19 statestack = list(stack)
20 20 statetokens = tokendefs[statestack[-1]]
21 21 while 1:
22 22 for rexmatch, action, new_state in statetokens:
23 23 m = rexmatch(text, pos)
24 24 if m:
25 25 if type(action) is _TokenType:
26 26 yield pos, action, m.group()
27 27 else:
28 28 for item in action(self, m):
29 29 yield item
30 30 pos = m.end()
31 31 if new_state is not None:
32 32 # state transition
33 33 if isinstance(new_state, tuple):
34 34 for state in new_state:
35 35 if state == '#pop':
36 36 statestack.pop()
37 37 elif state == '#push':
38 38 statestack.append(statestack[-1])
39 39 else:
40 40 statestack.append(state)
41 41 elif isinstance(new_state, int):
42 42 # pop
43 43 del statestack[new_state:]
44 44 elif new_state == '#push':
45 45 statestack.append(statestack[-1])
46 46 else:
47 47 assert False, "wrong state def: %r" % new_state
48 48 statetokens = tokendefs[statestack[-1]]
49 49 break
50 50 else:
51 51 try:
52 52 if text[pos] == '\n':
53 53 # at EOL, reset state to "root"
54 54 pos += 1
55 55 statestack = ['root']
56 56 statetokens = tokendefs['root']
57 57 yield pos, Text, u'\n'
58 58 continue
59 59 yield pos, Error, text[pos]
60 60 pos += 1
61 61 except IndexError:
62 62 break
63 63 self._saved_state_stack = list(statestack)
64 64
65 65 # Monkeypatch!
66 66 RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed
67 67
68 68
69 69 class PygmentsBlockUserData(QtGui.QTextBlockUserData):
70 70 """ Storage for the user data associated with each line.
71 71 """
72 72
73 73 syntax_stack = ('root',)
74 74
75 75 def __init__(self, **kwds):
76 76 for key, value in kwds.iteritems():
77 77 setattr(self, key, value)
78 78 QtGui.QTextBlockUserData.__init__(self)
79 79
80 80 def __repr__(self):
81 81 attrs = ['syntax_stack']
82 82 kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr))
83 83 for attr in attrs ])
84 84 return 'PygmentsBlockUserData(%s)' % kwds
85 85
86 86
87 87 class PygmentsHighlighter(QtGui.QSyntaxHighlighter):
88 88 """ Syntax highlighter that uses Pygments for parsing. """
89 89
90 90 #---------------------------------------------------------------------------
91 91 # 'QSyntaxHighlighter' interface
92 92 #---------------------------------------------------------------------------
93 93
94 94 def __init__(self, parent, lexer=None):
95 95 super(PygmentsHighlighter, self).__init__(parent)
96 96
97 97 self._document = QtGui.QTextDocument()
98 98 self._formatter = HtmlFormatter(nowrap=True)
99 99 self._lexer = lexer if lexer else PythonLexer()
100 100 self.set_style('default')
101 101
102 102 def highlightBlock(self, qstring):
103 103 """ Highlight a block of text.
104 104 """
105 105 qstring = unicode(qstring)
106 106 prev_data = self.currentBlock().previous().userData()
107 107
108 108 if prev_data is not None:
109 109 self._lexer._saved_state_stack = prev_data.syntax_stack
110 110 elif hasattr(self._lexer, '_saved_state_stack'):
111 111 del self._lexer._saved_state_stack
112 112
113 index = 0
114 113 # Lex the text using Pygments
114 index = 0
115 115 for token, text in self._lexer.get_tokens(qstring):
116 l = len(text)
117 format = self._get_format(token)
118 if format is not None:
119 self.setFormat(index, l, format)
120 index += l
116 length = len(text)
117 self.setFormat(index, length, self._get_format(token))
118 index += length
121 119
122 120 if hasattr(self._lexer, '_saved_state_stack'):
123 121 data = PygmentsBlockUserData(
124 122 syntax_stack=self._lexer._saved_state_stack)
125 123 self.currentBlock().setUserData(data)
126 124 # Clean up for the next go-round.
127 125 del self._lexer._saved_state_stack
128 126
129 127 #---------------------------------------------------------------------------
130 128 # 'PygmentsHighlighter' interface
131 129 #---------------------------------------------------------------------------
132 130
133 131 def set_style(self, style):
134 132 """ Sets the style to the specified Pygments style.
135 133 """
136 134 if isinstance(style, basestring):
137 135 style = get_style_by_name(style)
138 136 self._style = style
139 137 self._clear_caches()
140 138
141 139 def set_style_sheet(self, stylesheet):
142 140 """ Sets a CSS stylesheet. The classes in the stylesheet should
143 141 correspond to those generated by:
144 142
145 143 pygmentize -S <style> -f html
146 144
147 145 Note that 'set_style' and 'set_style_sheet' completely override each
148 146 other, i.e. they cannot be used in conjunction.
149 147 """
150 148 self._document.setDefaultStyleSheet(stylesheet)
151 149 self._style = None
152 150 self._clear_caches()
153 151
154 152 #---------------------------------------------------------------------------
155 153 # Protected interface
156 154 #---------------------------------------------------------------------------
157 155
158 156 def _clear_caches(self):
159 157 """ Clear caches for brushes and formats.
160 158 """
161 159 self._brushes = {}
162 160 self._formats = {}
163 161
164 162 def _get_format(self, token):
165 163 """ Returns a QTextCharFormat for token or None.
166 164 """
167 165 if token in self._formats:
168 166 return self._formats[token]
169 167
170 168 if self._style is None:
171 169 result = self._get_format_from_document(token, self._document)
172 170 else:
173 171 result = self._get_format_from_style(token, self._style)
174 172
175 173 self._formats[token] = result
176 174 return result
177 175
178 176 def _get_format_from_document(self, token, document):
179 177 """ Returns a QTextCharFormat for token by
180 178 """
181 179 code, html = self._formatter._format_lines([(token, 'dummy')]).next()
182 180 self._document.setHtml(html)
183 181 return QtGui.QTextCursor(self._document).charFormat()
184 182
185 183 def _get_format_from_style(self, token, style):
186 184 """ Returns a QTextCharFormat for token by reading a Pygments style.
187 185 """
188 result = None
186 result = QtGui.QTextCharFormat()
189 187 for key, value in style.style_for_token(token).items():
190 188 if value:
191 if result is None:
192 result = QtGui.QTextCharFormat()
193 189 if key == 'color':
194 190 result.setForeground(self._get_brush(value))
195 191 elif key == 'bgcolor':
196 192 result.setBackground(self._get_brush(value))
197 193 elif key == 'bold':
198 194 result.setFontWeight(QtGui.QFont.Bold)
199 195 elif key == 'italic':
200 196 result.setFontItalic(True)
201 197 elif key == 'underline':
202 198 result.setUnderlineStyle(
203 199 QtGui.QTextCharFormat.SingleUnderline)
204 200 elif key == 'sans':
205 201 result.setFontStyleHint(QtGui.QFont.SansSerif)
206 202 elif key == 'roman':
207 203 result.setFontStyleHint(QtGui.QFont.Times)
208 204 elif key == 'mono':
209 205 result.setFontStyleHint(QtGui.QFont.TypeWriter)
210 206 return result
211 207
212 208 def _get_brush(self, color):
213 209 """ Returns a brush for the color.
214 210 """
215 211 result = self._brushes.get(color)
216 212 if result is None:
217 213 qcolor = self._get_color(color)
218 214 result = QtGui.QBrush(qcolor)
219 215 self._brushes[color] = result
220 216 return result
221 217
222 218 def _get_color(self, color):
223 219 """ Returns a QColor built from a Pygments color string.
224 220 """
225 221 qcolor = QtGui.QColor()
226 222 qcolor.setRgb(int(color[:2], base=16),
227 223 int(color[2:4], base=16),
228 224 int(color[4:6], base=16))
229 225 return qcolor
230 226
General Comments 0
You need to be logged in to leave comments. Login now