|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a custom lexer using pygments. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 |
|
12 from pygments.token import Token |
|
13 from pygments.lexers import guess_lexer_for_filename, guess_lexer, find_lexer_class |
|
14 from pygments.util import ClassNotFound |
|
15 |
|
16 from PyQt4.QtGui import QColor, QFont |
|
17 |
|
18 from QScintilla.Lexers.LexerContainer import LexerContainer |
|
19 |
|
20 import Utilities |
|
21 |
|
22 PYGMENTS_DEFAULT, \ |
|
23 PYGMENTS_COMMENT, \ |
|
24 PYGMENTS_PREPROCESSOR, \ |
|
25 PYGMENTS_KEYWORD, \ |
|
26 PYGMENTS_PSEUDOKEYWORD, \ |
|
27 PYGMENTS_TYPEKEYWORD, \ |
|
28 PYGMENTS_OPERATOR, \ |
|
29 PYGMENTS_WORD, \ |
|
30 PYGMENTS_BUILTIN, \ |
|
31 PYGMENTS_FUNCTION, \ |
|
32 PYGMENTS_CLASS, \ |
|
33 PYGMENTS_NAMESPACE, \ |
|
34 PYGMENTS_EXCEPTION, \ |
|
35 PYGMENTS_VARIABLE, \ |
|
36 PYGMENTS_CONSTANT, \ |
|
37 PYGMENTS_LABEL, \ |
|
38 PYGMENTS_ENTITY, \ |
|
39 PYGMENTS_ATTRIBUTE, \ |
|
40 PYGMENTS_TAG, \ |
|
41 PYGMENTS_DECORATOR, \ |
|
42 PYGMENTS_STRING, \ |
|
43 PYGMENTS_DOCSTRING, \ |
|
44 PYGMENTS_SCALAR, \ |
|
45 PYGMENTS_ESCAPE, \ |
|
46 PYGMENTS_REGEX, \ |
|
47 PYGMENTS_SYMBOL, \ |
|
48 PYGMENTS_OTHER, \ |
|
49 PYGMENTS_NUMBER, \ |
|
50 PYGMENTS_HEADING, \ |
|
51 PYGMENTS_SUBHEADING, \ |
|
52 PYGMENTS_DELETED, \ |
|
53 PYGMENTS_INSERTED = range(32) |
|
54 # 32 to 39 are reserved for QScintilla internal styles |
|
55 PYGMENTS_GENERIC_ERROR, \ |
|
56 PYGMENTS_EMPHASIZE, \ |
|
57 PYGMENTS_STRONG, \ |
|
58 PYGMENTS_PROMPT, \ |
|
59 PYGMENTS_OUTPUT, \ |
|
60 PYGMENTS_TRACEBACK, \ |
|
61 PYGMENTS_ERROR = range(40, 47) |
|
62 |
|
63 #-----------------------------------------------------------------------------# |
|
64 |
|
65 TOKEN_MAP = { |
|
66 Token.Comment: PYGMENTS_COMMENT, |
|
67 Token.Comment.Preproc: PYGMENTS_PREPROCESSOR, |
|
68 |
|
69 Token.Keyword: PYGMENTS_KEYWORD, |
|
70 Token.Keyword.Pseudo: PYGMENTS_PSEUDOKEYWORD, |
|
71 Token.Keyword.Type: PYGMENTS_TYPEKEYWORD, |
|
72 |
|
73 Token.Operator: PYGMENTS_OPERATOR, |
|
74 Token.Operator.Word: PYGMENTS_WORD, |
|
75 |
|
76 Token.Name.Builtin: PYGMENTS_BUILTIN, |
|
77 Token.Name.Function: PYGMENTS_FUNCTION, |
|
78 Token.Name.Class: PYGMENTS_CLASS, |
|
79 Token.Name.Namespace: PYGMENTS_NAMESPACE, |
|
80 Token.Name.Exception: PYGMENTS_EXCEPTION, |
|
81 Token.Name.Variable: PYGMENTS_VARIABLE, |
|
82 Token.Name.Constant: PYGMENTS_CONSTANT, |
|
83 Token.Name.Label: PYGMENTS_LABEL, |
|
84 Token.Name.Entity: PYGMENTS_ENTITY, |
|
85 Token.Name.Attribute: PYGMENTS_ATTRIBUTE, |
|
86 Token.Name.Tag: PYGMENTS_TAG, |
|
87 Token.Name.Decorator: PYGMENTS_DECORATOR, |
|
88 |
|
89 Token.String: PYGMENTS_STRING, |
|
90 Token.String.Doc: PYGMENTS_DOCSTRING, |
|
91 Token.String.Interpol: PYGMENTS_SCALAR, |
|
92 Token.String.Escape: PYGMENTS_ESCAPE, |
|
93 Token.String.Regex: PYGMENTS_REGEX, |
|
94 Token.String.Symbol: PYGMENTS_SYMBOL, |
|
95 Token.String.Other: PYGMENTS_OTHER, |
|
96 Token.Number: PYGMENTS_NUMBER, |
|
97 |
|
98 Token.Generic.Heading: PYGMENTS_HEADING, |
|
99 Token.Generic.Subheading: PYGMENTS_SUBHEADING, |
|
100 Token.Generic.Deleted: PYGMENTS_DELETED, |
|
101 Token.Generic.Inserted: PYGMENTS_INSERTED, |
|
102 Token.Generic.Error: PYGMENTS_GENERIC_ERROR, |
|
103 Token.Generic.Emph: PYGMENTS_EMPHASIZE, |
|
104 Token.Generic.Strong: PYGMENTS_STRONG, |
|
105 Token.Generic.Prompt: PYGMENTS_PROMPT, |
|
106 Token.Generic.Output: PYGMENTS_OUTPUT, |
|
107 Token.Generic.Traceback: PYGMENTS_TRACEBACK, |
|
108 |
|
109 Token.Error: PYGMENTS_ERROR, |
|
110 } |
|
111 |
|
112 #-----------------------------------------------------------------------------# |
|
113 |
|
114 class LexerPygments(LexerContainer): |
|
115 """ |
|
116 Class implementing a custom lexer using pygments. |
|
117 """ |
|
118 def __init__(self, parent = None, name = ""): |
|
119 """ |
|
120 Constructor |
|
121 |
|
122 @param parent parent widget of this lexer |
|
123 @keyparam name name of the pygments lexer to use (string) |
|
124 """ |
|
125 LexerContainer.__init__(self, parent) |
|
126 |
|
127 self.__pygmentsName = name |
|
128 |
|
129 self.descriptions = { |
|
130 PYGMENTS_DEFAULT : self.trUtf8("Default"), |
|
131 PYGMENTS_COMMENT : self.trUtf8("Comment"), |
|
132 PYGMENTS_PREPROCESSOR : self.trUtf8("Preprocessor"), |
|
133 PYGMENTS_KEYWORD : self.trUtf8("Keyword"), |
|
134 PYGMENTS_PSEUDOKEYWORD : self.trUtf8("Pseudo Keyword"), |
|
135 PYGMENTS_TYPEKEYWORD : self.trUtf8("Type Keyword"), |
|
136 PYGMENTS_OPERATOR : self.trUtf8("Operator"), |
|
137 PYGMENTS_WORD : self.trUtf8("Word"), |
|
138 PYGMENTS_BUILTIN : self.trUtf8("Builtin"), |
|
139 PYGMENTS_FUNCTION : self.trUtf8("Function or method name"), |
|
140 PYGMENTS_CLASS : self.trUtf8("Class name"), |
|
141 PYGMENTS_NAMESPACE : self.trUtf8("Namespace"), |
|
142 PYGMENTS_EXCEPTION : self.trUtf8("Exception"), |
|
143 PYGMENTS_VARIABLE : self.trUtf8("Identifier"), |
|
144 PYGMENTS_CONSTANT : self.trUtf8("Constant"), |
|
145 PYGMENTS_LABEL : self.trUtf8("Label"), |
|
146 PYGMENTS_ENTITY : self.trUtf8("Entity"), |
|
147 PYGMENTS_ATTRIBUTE : self.trUtf8("Attribute"), |
|
148 PYGMENTS_TAG : self.trUtf8("Tag"), |
|
149 PYGMENTS_DECORATOR : self.trUtf8("Decorator"), |
|
150 PYGMENTS_STRING : self.trUtf8("String"), |
|
151 PYGMENTS_DOCSTRING : self.trUtf8("Documentation string"), |
|
152 PYGMENTS_SCALAR : self.trUtf8("Scalar"), |
|
153 PYGMENTS_ESCAPE : self.trUtf8("Escape"), |
|
154 PYGMENTS_REGEX : self.trUtf8("Regular expression"), |
|
155 PYGMENTS_SYMBOL : self.trUtf8("Symbol"), |
|
156 PYGMENTS_OTHER : self.trUtf8("Other string"), |
|
157 PYGMENTS_NUMBER : self.trUtf8("Number"), |
|
158 PYGMENTS_HEADING : self.trUtf8("Heading"), |
|
159 PYGMENTS_SUBHEADING : self.trUtf8("Subheading"), |
|
160 PYGMENTS_DELETED : self.trUtf8("Deleted"), |
|
161 PYGMENTS_INSERTED : self.trUtf8("Inserted"), |
|
162 PYGMENTS_GENERIC_ERROR : self.trUtf8("Generic error"), |
|
163 PYGMENTS_EMPHASIZE : self.trUtf8("Emphasized text"), |
|
164 PYGMENTS_STRONG : self.trUtf8("Strong text"), |
|
165 PYGMENTS_PROMPT : self.trUtf8("Prompt"), |
|
166 PYGMENTS_OUTPUT : self.trUtf8("Output"), |
|
167 PYGMENTS_TRACEBACK : self.trUtf8("Traceback"), |
|
168 PYGMENTS_ERROR : self.trUtf8("Error"), |
|
169 } |
|
170 |
|
171 self.defaultColors = { |
|
172 PYGMENTS_DEFAULT : QColor("#000000"), |
|
173 PYGMENTS_COMMENT : QColor("#408080"), |
|
174 PYGMENTS_PREPROCESSOR : QColor("#BC7A00"), |
|
175 PYGMENTS_KEYWORD : QColor("#008000"), |
|
176 PYGMENTS_PSEUDOKEYWORD : QColor("#008000"), |
|
177 PYGMENTS_TYPEKEYWORD : QColor("#B00040"), |
|
178 PYGMENTS_OPERATOR : QColor("#666666"), |
|
179 PYGMENTS_WORD : QColor("#AA22FF"), |
|
180 PYGMENTS_BUILTIN : QColor("#008000"), |
|
181 PYGMENTS_FUNCTION : QColor("#0000FF"), |
|
182 PYGMENTS_CLASS : QColor("#0000FF"), |
|
183 PYGMENTS_NAMESPACE : QColor("#0000FF"), |
|
184 PYGMENTS_EXCEPTION : QColor("#D2413A"), |
|
185 PYGMENTS_VARIABLE : QColor("#19177C"), |
|
186 PYGMENTS_CONSTANT : QColor("#880000"), |
|
187 PYGMENTS_LABEL : QColor("#A0A000"), |
|
188 PYGMENTS_ENTITY : QColor("#999999"), |
|
189 PYGMENTS_ATTRIBUTE : QColor("#7D9029"), |
|
190 PYGMENTS_TAG : QColor("#008000"), |
|
191 PYGMENTS_DECORATOR : QColor("#AA22FF"), |
|
192 PYGMENTS_STRING : QColor("#BA2121"), |
|
193 PYGMENTS_DOCSTRING : QColor("#BA2121"), |
|
194 PYGMENTS_SCALAR : QColor("#BB6688"), |
|
195 PYGMENTS_ESCAPE : QColor("#BB6622"), |
|
196 PYGMENTS_REGEX : QColor("#BB6688"), |
|
197 PYGMENTS_SYMBOL : QColor("#19177C"), |
|
198 PYGMENTS_OTHER : QColor("#008000"), |
|
199 PYGMENTS_NUMBER : QColor("#666666"), |
|
200 PYGMENTS_HEADING : QColor("#000080"), |
|
201 PYGMENTS_SUBHEADING : QColor("#800080"), |
|
202 PYGMENTS_DELETED : QColor("#A00000"), |
|
203 PYGMENTS_INSERTED : QColor("#00A000"), |
|
204 PYGMENTS_GENERIC_ERROR : QColor("#FF0000"), |
|
205 PYGMENTS_PROMPT : QColor("#000080"), |
|
206 PYGMENTS_OUTPUT : QColor("#808080"), |
|
207 PYGMENTS_TRACEBACK : QColor("#0040D0"), |
|
208 } |
|
209 |
|
210 self.defaultPapers = { |
|
211 PYGMENTS_ERROR : QColor("#FF0000"), |
|
212 } |
|
213 |
|
214 def language(self): |
|
215 """ |
|
216 Public method returning the language of the lexer. |
|
217 |
|
218 @return language of the lexer (string) |
|
219 """ |
|
220 return "Guessed" |
|
221 |
|
222 def description(self, style): |
|
223 """ |
|
224 Public method returning the descriptions of the styles supported |
|
225 by the lexer. |
|
226 |
|
227 @param style style number (integer) |
|
228 @return description for the style (string) |
|
229 """ |
|
230 try: |
|
231 return self.descriptions[style] |
|
232 except KeyError: |
|
233 return "" |
|
234 |
|
235 def defaultColor(self, style): |
|
236 """ |
|
237 Public method to get the default foreground color for a style. |
|
238 |
|
239 @param style style number (integer) |
|
240 @return foreground color (QColor) |
|
241 """ |
|
242 try: |
|
243 return self.defaultColors[style] |
|
244 except KeyError: |
|
245 return LexerContainer.defaultColor(self, style) |
|
246 |
|
247 def defaultPaper(self, style): |
|
248 """ |
|
249 Public method to get the default background color for a style. |
|
250 |
|
251 @param style style number (integer) |
|
252 @return background color (QColor) |
|
253 """ |
|
254 try: |
|
255 return self.defaultPapers[style] |
|
256 except KeyError: |
|
257 return LexerContainer.defaultPaper(self, style) |
|
258 |
|
259 def defaultFont(self, style): |
|
260 """ |
|
261 Public method to get the default font for a style. |
|
262 |
|
263 @param style style number (integer) |
|
264 @return font (QFont) |
|
265 """ |
|
266 if style in [PYGMENTS_COMMENT, PYGMENTS_PREPROCESSOR]: |
|
267 if Utilities.isWindowsPlatform(): |
|
268 f = QFont("Comic Sans MS", 9) |
|
269 else: |
|
270 f = QFont("Bitstream Vera Serif", 9) |
|
271 if style == PYGMENTS_PREPROCESSOR: |
|
272 f.setItalic(True) |
|
273 return f |
|
274 |
|
275 if style in [PYGMENTS_STRING]: |
|
276 if Utilities.isWindowsPlatform(): |
|
277 return QFont("Comic Sans MS", 10) |
|
278 else: |
|
279 return QFont("Bitstream Vera Serif", 10) |
|
280 |
|
281 if style in [PYGMENTS_KEYWORD, PYGMENTS_OPERATOR, PYGMENTS_WORD, PYGMENTS_BUILTIN, |
|
282 PYGMENTS_ATTRIBUTE, PYGMENTS_FUNCTION, PYGMENTS_CLASS, |
|
283 PYGMENTS_NAMESPACE, PYGMENTS_EXCEPTION, PYGMENTS_ENTITY, |
|
284 PYGMENTS_TAG, PYGMENTS_SCALAR, PYGMENTS_ESCAPE, PYGMENTS_HEADING, |
|
285 PYGMENTS_SUBHEADING, PYGMENTS_STRONG, PYGMENTS_PROMPT]: |
|
286 f = LexerContainer.defaultFont(self, style) |
|
287 f.setBold(True) |
|
288 return f |
|
289 |
|
290 if style in [PYGMENTS_DOCSTRING, PYGMENTS_EMPHASIZE]: |
|
291 f = LexerContainer.defaultFont(self, style) |
|
292 f.setItalic(True) |
|
293 return f |
|
294 |
|
295 return LexerContainer.defaultFont(self, style) |
|
296 |
|
297 def styleBitsNeeded(self): |
|
298 """ |
|
299 Public method to get the number of style bits needed by the lexer. |
|
300 |
|
301 @return number of style bits needed (integer) |
|
302 """ |
|
303 return 6 |
|
304 |
|
305 def __guessLexer(self, text): |
|
306 """ |
|
307 Private method to guess a pygments lexer. |
|
308 |
|
309 @param text text to base guessing on (string) |
|
310 @return reference to the guessed lexer (pygments.lexer) |
|
311 """ |
|
312 lexer = None |
|
313 |
|
314 if self.__pygmentsName: |
|
315 lexerClass = find_lexer_class(self.__pygmentsName) |
|
316 if lexerClass is not None: |
|
317 lexer = lexerClass() |
|
318 |
|
319 elif text: |
|
320 # step 1: guess based on filename and text |
|
321 if self.editor is not None: |
|
322 fn = self.editor.getFileName() |
|
323 try: |
|
324 lexer = guess_lexer_for_filename(fn, text) |
|
325 except ClassNotFound: |
|
326 pass |
|
327 |
|
328 # step 2: guess on text only |
|
329 if lexer is None: |
|
330 try: |
|
331 lexer = guess_lexer(text) |
|
332 except ClassNotFound: |
|
333 pass |
|
334 |
|
335 return lexer |
|
336 |
|
337 def canStyle(self): |
|
338 """ |
|
339 Public method to check, if the lexer is able to style the text. |
|
340 |
|
341 @return flag indicating the lexer capability (boolean) |
|
342 """ |
|
343 if self.editor is None: |
|
344 return True |
|
345 |
|
346 text = self.editor.text().encode('utf-8') |
|
347 self.__lexer = self.__guessLexer(text) |
|
348 |
|
349 return self.__lexer is not None |
|
350 |
|
351 def name(self): |
|
352 """ |
|
353 Public method to get the name of the pygments lexer. |
|
354 |
|
355 @return name of the pygments lexer (string) |
|
356 """ |
|
357 if self.__lexer is None: |
|
358 return "" |
|
359 else: |
|
360 return self.__lexer.name |
|
361 |
|
362 def styleText(self, start, end): |
|
363 """ |
|
364 Public method to perform the styling. |
|
365 |
|
366 @param start position of first character to be styled (integer) |
|
367 @param end position of last character to be styled (integer) |
|
368 """ |
|
369 text = self.editor.text()[:end + 1].encode('utf-8') |
|
370 self.__lexer = self.__guessLexer(text) |
|
371 |
|
372 cpos = 0 |
|
373 self.editor.startStyling(cpos, 0x3f) |
|
374 if self.__lexer is None: |
|
375 self.editor.setStyling(len(text), PYGMENTS_DEFAULT) |
|
376 else: |
|
377 eolLen = len(self.editor.getLineSeparator()) |
|
378 for token, txt in self.__lexer.get_tokens(text): |
|
379 style = TOKEN_MAP.get(token, PYGMENTS_DEFAULT) |
|
380 |
|
381 tlen = len(txt) |
|
382 if eolLen > 1: |
|
383 tlen += txt.count('\n') |
|
384 if tlen: |
|
385 self.editor.setStyling(tlen, style) |
|
386 cpos += tlen |
|
387 self.editor.startStyling(cpos, 0x3f) |
|
388 |
|
389 def isCommentStyle(self, style): |
|
390 """ |
|
391 Public method to check, if a style is a comment style. |
|
392 |
|
393 @return flag indicating a comment style (boolean) |
|
394 """ |
|
395 return style in [PYGMENTS_COMMENT] |
|
396 |
|
397 def isStringStyle(self, style): |
|
398 """ |
|
399 Public method to check, if a style is a string style. |
|
400 |
|
401 @return flag indicating a string style (boolean) |
|
402 """ |
|
403 return style in [PYGMENTS_STRING, PYGMENTS_DOCSTRING, PYGMENTS_OTHER, |
|
404 PYGMENTS_HEADING, PYGMENTS_SUBHEADING, PYGMENTS_EMPHASIZE, |
|
405 PYGMENTS_STRONG] |