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