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