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