##// END OF EJS Templates
token passed to format_lines must be unicode, not str...
MinRK -
Show More
@@ -1,224 +1,224 b''
1 # System library imports.
1 # System library imports.
2 from IPython.external.qt import QtGui
2 from IPython.external.qt import QtGui
3 from pygments.formatters.html import HtmlFormatter
3 from pygments.formatters.html import HtmlFormatter
4 from pygments.lexer import RegexLexer, _TokenType, Text, Error
4 from pygments.lexer import RegexLexer, _TokenType, Text, Error
5 from pygments.lexers import PythonLexer
5 from pygments.lexers import PythonLexer
6 from pygments.styles import get_style_by_name
6 from pygments.styles import get_style_by_name
7
7
8
8
9 def get_tokens_unprocessed(self, text, stack=('root',)):
9 def get_tokens_unprocessed(self, text, stack=('root',)):
10 """ Split ``text`` into (tokentype, text) pairs.
10 """ Split ``text`` into (tokentype, text) pairs.
11
11
12 Monkeypatched to store the final stack on the object itself.
12 Monkeypatched to store the final stack on the object itself.
13 """
13 """
14 pos = 0
14 pos = 0
15 tokendefs = self._tokens
15 tokendefs = self._tokens
16 if hasattr(self, '_saved_state_stack'):
16 if hasattr(self, '_saved_state_stack'):
17 statestack = list(self._saved_state_stack)
17 statestack = list(self._saved_state_stack)
18 else:
18 else:
19 statestack = list(stack)
19 statestack = list(stack)
20 statetokens = tokendefs[statestack[-1]]
20 statetokens = tokendefs[statestack[-1]]
21 while 1:
21 while 1:
22 for rexmatch, action, new_state in statetokens:
22 for rexmatch, action, new_state in statetokens:
23 m = rexmatch(text, pos)
23 m = rexmatch(text, pos)
24 if m:
24 if m:
25 if type(action) is _TokenType:
25 if type(action) is _TokenType:
26 yield pos, action, m.group()
26 yield pos, action, m.group()
27 else:
27 else:
28 for item in action(self, m):
28 for item in action(self, m):
29 yield item
29 yield item
30 pos = m.end()
30 pos = m.end()
31 if new_state is not None:
31 if new_state is not None:
32 # state transition
32 # state transition
33 if isinstance(new_state, tuple):
33 if isinstance(new_state, tuple):
34 for state in new_state:
34 for state in new_state:
35 if state == '#pop':
35 if state == '#pop':
36 statestack.pop()
36 statestack.pop()
37 elif state == '#push':
37 elif state == '#push':
38 statestack.append(statestack[-1])
38 statestack.append(statestack[-1])
39 else:
39 else:
40 statestack.append(state)
40 statestack.append(state)
41 elif isinstance(new_state, int):
41 elif isinstance(new_state, int):
42 # pop
42 # pop
43 del statestack[new_state:]
43 del statestack[new_state:]
44 elif new_state == '#push':
44 elif new_state == '#push':
45 statestack.append(statestack[-1])
45 statestack.append(statestack[-1])
46 else:
46 else:
47 assert False, "wrong state def: %r" % new_state
47 assert False, "wrong state def: %r" % new_state
48 statetokens = tokendefs[statestack[-1]]
48 statetokens = tokendefs[statestack[-1]]
49 break
49 break
50 else:
50 else:
51 try:
51 try:
52 if text[pos] == '\n':
52 if text[pos] == '\n':
53 # at EOL, reset state to "root"
53 # at EOL, reset state to "root"
54 pos += 1
54 pos += 1
55 statestack = ['root']
55 statestack = ['root']
56 statetokens = tokendefs['root']
56 statetokens = tokendefs['root']
57 yield pos, Text, u'\n'
57 yield pos, Text, u'\n'
58 continue
58 continue
59 yield pos, Error, text[pos]
59 yield pos, Error, text[pos]
60 pos += 1
60 pos += 1
61 except IndexError:
61 except IndexError:
62 break
62 break
63 self._saved_state_stack = list(statestack)
63 self._saved_state_stack = list(statestack)
64
64
65 # Monkeypatch!
65 # Monkeypatch!
66 RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed
66 RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed
67
67
68
68
69 class PygmentsBlockUserData(QtGui.QTextBlockUserData):
69 class PygmentsBlockUserData(QtGui.QTextBlockUserData):
70 """ Storage for the user data associated with each line.
70 """ Storage for the user data associated with each line.
71 """
71 """
72
72
73 syntax_stack = ('root',)
73 syntax_stack = ('root',)
74
74
75 def __init__(self, **kwds):
75 def __init__(self, **kwds):
76 for key, value in kwds.iteritems():
76 for key, value in kwds.iteritems():
77 setattr(self, key, value)
77 setattr(self, key, value)
78 QtGui.QTextBlockUserData.__init__(self)
78 QtGui.QTextBlockUserData.__init__(self)
79
79
80 def __repr__(self):
80 def __repr__(self):
81 attrs = ['syntax_stack']
81 attrs = ['syntax_stack']
82 kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr))
82 kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr))
83 for attr in attrs ])
83 for attr in attrs ])
84 return 'PygmentsBlockUserData(%s)' % kwds
84 return 'PygmentsBlockUserData(%s)' % kwds
85
85
86
86
87 class PygmentsHighlighter(QtGui.QSyntaxHighlighter):
87 class PygmentsHighlighter(QtGui.QSyntaxHighlighter):
88 """ Syntax highlighter that uses Pygments for parsing. """
88 """ Syntax highlighter that uses Pygments for parsing. """
89
89
90 #---------------------------------------------------------------------------
90 #---------------------------------------------------------------------------
91 # 'QSyntaxHighlighter' interface
91 # 'QSyntaxHighlighter' interface
92 #---------------------------------------------------------------------------
92 #---------------------------------------------------------------------------
93
93
94 def __init__(self, parent, lexer=None):
94 def __init__(self, parent, lexer=None):
95 super(PygmentsHighlighter, self).__init__(parent)
95 super(PygmentsHighlighter, self).__init__(parent)
96
96
97 self._document = QtGui.QTextDocument()
97 self._document = QtGui.QTextDocument()
98 self._formatter = HtmlFormatter(nowrap=True)
98 self._formatter = HtmlFormatter(nowrap=True)
99 self._lexer = lexer if lexer else PythonLexer()
99 self._lexer = lexer if lexer else PythonLexer()
100 self.set_style('default')
100 self.set_style('default')
101
101
102 def highlightBlock(self, string):
102 def highlightBlock(self, string):
103 """ Highlight a block of text.
103 """ Highlight a block of text.
104 """
104 """
105 prev_data = self.currentBlock().previous().userData()
105 prev_data = self.currentBlock().previous().userData()
106 if prev_data is not None:
106 if prev_data is not None:
107 self._lexer._saved_state_stack = prev_data.syntax_stack
107 self._lexer._saved_state_stack = prev_data.syntax_stack
108 elif hasattr(self._lexer, '_saved_state_stack'):
108 elif hasattr(self._lexer, '_saved_state_stack'):
109 del self._lexer._saved_state_stack
109 del self._lexer._saved_state_stack
110
110
111 # Lex the text using Pygments
111 # Lex the text using Pygments
112 index = 0
112 index = 0
113 for token, text in self._lexer.get_tokens(string):
113 for token, text in self._lexer.get_tokens(string):
114 length = len(text)
114 length = len(text)
115 self.setFormat(index, length, self._get_format(token))
115 self.setFormat(index, length, self._get_format(token))
116 index += length
116 index += length
117
117
118 if hasattr(self._lexer, '_saved_state_stack'):
118 if hasattr(self._lexer, '_saved_state_stack'):
119 data = PygmentsBlockUserData(
119 data = PygmentsBlockUserData(
120 syntax_stack=self._lexer._saved_state_stack)
120 syntax_stack=self._lexer._saved_state_stack)
121 self.currentBlock().setUserData(data)
121 self.currentBlock().setUserData(data)
122 # Clean up for the next go-round.
122 # Clean up for the next go-round.
123 del self._lexer._saved_state_stack
123 del self._lexer._saved_state_stack
124
124
125 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
126 # 'PygmentsHighlighter' interface
126 # 'PygmentsHighlighter' interface
127 #---------------------------------------------------------------------------
127 #---------------------------------------------------------------------------
128
128
129 def set_style(self, style):
129 def set_style(self, style):
130 """ Sets the style to the specified Pygments style.
130 """ Sets the style to the specified Pygments style.
131 """
131 """
132 if isinstance(style, basestring):
132 if isinstance(style, basestring):
133 style = get_style_by_name(style)
133 style = get_style_by_name(style)
134 self._style = style
134 self._style = style
135 self._clear_caches()
135 self._clear_caches()
136
136
137 def set_style_sheet(self, stylesheet):
137 def set_style_sheet(self, stylesheet):
138 """ Sets a CSS stylesheet. The classes in the stylesheet should
138 """ Sets a CSS stylesheet. The classes in the stylesheet should
139 correspond to those generated by:
139 correspond to those generated by:
140
140
141 pygmentize -S <style> -f html
141 pygmentize -S <style> -f html
142
142
143 Note that 'set_style' and 'set_style_sheet' completely override each
143 Note that 'set_style' and 'set_style_sheet' completely override each
144 other, i.e. they cannot be used in conjunction.
144 other, i.e. they cannot be used in conjunction.
145 """
145 """
146 self._document.setDefaultStyleSheet(stylesheet)
146 self._document.setDefaultStyleSheet(stylesheet)
147 self._style = None
147 self._style = None
148 self._clear_caches()
148 self._clear_caches()
149
149
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151 # Protected interface
151 # Protected interface
152 #---------------------------------------------------------------------------
152 #---------------------------------------------------------------------------
153
153
154 def _clear_caches(self):
154 def _clear_caches(self):
155 """ Clear caches for brushes and formats.
155 """ Clear caches for brushes and formats.
156 """
156 """
157 self._brushes = {}
157 self._brushes = {}
158 self._formats = {}
158 self._formats = {}
159
159
160 def _get_format(self, token):
160 def _get_format(self, token):
161 """ Returns a QTextCharFormat for token or None.
161 """ Returns a QTextCharFormat for token or None.
162 """
162 """
163 if token in self._formats:
163 if token in self._formats:
164 return self._formats[token]
164 return self._formats[token]
165
165
166 if self._style is None:
166 if self._style is None:
167 result = self._get_format_from_document(token, self._document)
167 result = self._get_format_from_document(token, self._document)
168 else:
168 else:
169 result = self._get_format_from_style(token, self._style)
169 result = self._get_format_from_style(token, self._style)
170
170
171 self._formats[token] = result
171 self._formats[token] = result
172 return result
172 return result
173
173
174 def _get_format_from_document(self, token, document):
174 def _get_format_from_document(self, token, document):
175 """ Returns a QTextCharFormat for token by
175 """ Returns a QTextCharFormat for token by
176 """
176 """
177 code, html = self._formatter._format_lines([(token, 'dummy')]).next()
177 code, html = self._formatter._format_lines([(token, u'dummy')]).next()
178 self._document.setHtml(html)
178 self._document.setHtml(html)
179 return QtGui.QTextCursor(self._document).charFormat()
179 return QtGui.QTextCursor(self._document).charFormat()
180
180
181 def _get_format_from_style(self, token, style):
181 def _get_format_from_style(self, token, style):
182 """ Returns a QTextCharFormat for token by reading a Pygments style.
182 """ Returns a QTextCharFormat for token by reading a Pygments style.
183 """
183 """
184 result = QtGui.QTextCharFormat()
184 result = QtGui.QTextCharFormat()
185 for key, value in style.style_for_token(token).items():
185 for key, value in style.style_for_token(token).items():
186 if value:
186 if value:
187 if key == 'color':
187 if key == 'color':
188 result.setForeground(self._get_brush(value))
188 result.setForeground(self._get_brush(value))
189 elif key == 'bgcolor':
189 elif key == 'bgcolor':
190 result.setBackground(self._get_brush(value))
190 result.setBackground(self._get_brush(value))
191 elif key == 'bold':
191 elif key == 'bold':
192 result.setFontWeight(QtGui.QFont.Bold)
192 result.setFontWeight(QtGui.QFont.Bold)
193 elif key == 'italic':
193 elif key == 'italic':
194 result.setFontItalic(True)
194 result.setFontItalic(True)
195 elif key == 'underline':
195 elif key == 'underline':
196 result.setUnderlineStyle(
196 result.setUnderlineStyle(
197 QtGui.QTextCharFormat.SingleUnderline)
197 QtGui.QTextCharFormat.SingleUnderline)
198 elif key == 'sans':
198 elif key == 'sans':
199 result.setFontStyleHint(QtGui.QFont.SansSerif)
199 result.setFontStyleHint(QtGui.QFont.SansSerif)
200 elif key == 'roman':
200 elif key == 'roman':
201 result.setFontStyleHint(QtGui.QFont.Times)
201 result.setFontStyleHint(QtGui.QFont.Times)
202 elif key == 'mono':
202 elif key == 'mono':
203 result.setFontStyleHint(QtGui.QFont.TypeWriter)
203 result.setFontStyleHint(QtGui.QFont.TypeWriter)
204 return result
204 return result
205
205
206 def _get_brush(self, color):
206 def _get_brush(self, color):
207 """ Returns a brush for the color.
207 """ Returns a brush for the color.
208 """
208 """
209 result = self._brushes.get(color)
209 result = self._brushes.get(color)
210 if result is None:
210 if result is None:
211 qcolor = self._get_color(color)
211 qcolor = self._get_color(color)
212 result = QtGui.QBrush(qcolor)
212 result = QtGui.QBrush(qcolor)
213 self._brushes[color] = result
213 self._brushes[color] = result
214 return result
214 return result
215
215
216 def _get_color(self, color):
216 def _get_color(self, color):
217 """ Returns a QColor built from a Pygments color string.
217 """ Returns a QColor built from a Pygments color string.
218 """
218 """
219 qcolor = QtGui.QColor()
219 qcolor = QtGui.QColor()
220 qcolor.setRgb(int(color[:2], base=16),
220 qcolor.setRgb(int(color[:2], base=16),
221 int(color[2:4], base=16),
221 int(color[2:4], base=16),
222 int(color[4:6], base=16))
222 int(color[4:6], base=16))
223 return qcolor
223 return qcolor
224
224
General Comments 0
You need to be logged in to leave comments. Login now