##// END OF EJS Templates
token passed to format_lines must be unicode, not str...
MinRK -
Show More
@@ -1,224 +1,224 b''
1 1 # System library imports.
2 2 from IPython.external.qt 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, string):
103 103 """ Highlight a block of text.
104 104 """
105 105 prev_data = self.currentBlock().previous().userData()
106 106 if prev_data is not None:
107 107 self._lexer._saved_state_stack = prev_data.syntax_stack
108 108 elif hasattr(self._lexer, '_saved_state_stack'):
109 109 del self._lexer._saved_state_stack
110 110
111 111 # Lex the text using Pygments
112 112 index = 0
113 113 for token, text in self._lexer.get_tokens(string):
114 114 length = len(text)
115 115 self.setFormat(index, length, self._get_format(token))
116 116 index += length
117 117
118 118 if hasattr(self._lexer, '_saved_state_stack'):
119 119 data = PygmentsBlockUserData(
120 120 syntax_stack=self._lexer._saved_state_stack)
121 121 self.currentBlock().setUserData(data)
122 122 # Clean up for the next go-round.
123 123 del self._lexer._saved_state_stack
124 124
125 125 #---------------------------------------------------------------------------
126 126 # 'PygmentsHighlighter' interface
127 127 #---------------------------------------------------------------------------
128 128
129 129 def set_style(self, style):
130 130 """ Sets the style to the specified Pygments style.
131 131 """
132 132 if isinstance(style, basestring):
133 133 style = get_style_by_name(style)
134 134 self._style = style
135 135 self._clear_caches()
136 136
137 137 def set_style_sheet(self, stylesheet):
138 138 """ Sets a CSS stylesheet. The classes in the stylesheet should
139 139 correspond to those generated by:
140 140
141 141 pygmentize -S <style> -f html
142 142
143 143 Note that 'set_style' and 'set_style_sheet' completely override each
144 144 other, i.e. they cannot be used in conjunction.
145 145 """
146 146 self._document.setDefaultStyleSheet(stylesheet)
147 147 self._style = None
148 148 self._clear_caches()
149 149
150 150 #---------------------------------------------------------------------------
151 151 # Protected interface
152 152 #---------------------------------------------------------------------------
153 153
154 154 def _clear_caches(self):
155 155 """ Clear caches for brushes and formats.
156 156 """
157 157 self._brushes = {}
158 158 self._formats = {}
159 159
160 160 def _get_format(self, token):
161 161 """ Returns a QTextCharFormat for token or None.
162 162 """
163 163 if token in self._formats:
164 164 return self._formats[token]
165 165
166 166 if self._style is None:
167 167 result = self._get_format_from_document(token, self._document)
168 168 else:
169 169 result = self._get_format_from_style(token, self._style)
170 170
171 171 self._formats[token] = result
172 172 return result
173 173
174 174 def _get_format_from_document(self, token, document):
175 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 178 self._document.setHtml(html)
179 179 return QtGui.QTextCursor(self._document).charFormat()
180 180
181 181 def _get_format_from_style(self, token, style):
182 182 """ Returns a QTextCharFormat for token by reading a Pygments style.
183 183 """
184 184 result = QtGui.QTextCharFormat()
185 185 for key, value in style.style_for_token(token).items():
186 186 if value:
187 187 if key == 'color':
188 188 result.setForeground(self._get_brush(value))
189 189 elif key == 'bgcolor':
190 190 result.setBackground(self._get_brush(value))
191 191 elif key == 'bold':
192 192 result.setFontWeight(QtGui.QFont.Bold)
193 193 elif key == 'italic':
194 194 result.setFontItalic(True)
195 195 elif key == 'underline':
196 196 result.setUnderlineStyle(
197 197 QtGui.QTextCharFormat.SingleUnderline)
198 198 elif key == 'sans':
199 199 result.setFontStyleHint(QtGui.QFont.SansSerif)
200 200 elif key == 'roman':
201 201 result.setFontStyleHint(QtGui.QFont.Times)
202 202 elif key == 'mono':
203 203 result.setFontStyleHint(QtGui.QFont.TypeWriter)
204 204 return result
205 205
206 206 def _get_brush(self, color):
207 207 """ Returns a brush for the color.
208 208 """
209 209 result = self._brushes.get(color)
210 210 if result is None:
211 211 qcolor = self._get_color(color)
212 212 result = QtGui.QBrush(qcolor)
213 213 self._brushes[color] = result
214 214 return result
215 215
216 216 def _get_color(self, color):
217 217 """ Returns a QColor built from a Pygments color string.
218 218 """
219 219 qcolor = QtGui.QColor()
220 220 qcolor.setRgb(int(color[:2], base=16),
221 221 int(color[2:4], base=16),
222 222 int(color[4:6], base=16))
223 223 return qcolor
224 224
General Comments 0
You need to be logged in to leave comments. Login now