src/eric7/QScintilla/Lexers/LexerPygments.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a custom lexer using pygments.
8 """
9
10 import contextlib
11
12 from pygments.token import Token
13 from pygments.lexers import (
14 guess_lexer_for_filename, guess_lexer, find_lexer_class
15 )
16 from pygments.util import ClassNotFound
17
18 from PyQt6.QtGui import QColor, QFont
19
20 from QScintilla.Lexers.LexerContainer import LexerContainer
21
22 import Utilities
23
24 PYGMENTS_DEFAULT = 0
25 PYGMENTS_COMMENT = 1
26 PYGMENTS_PREPROCESSOR = 2
27 PYGMENTS_KEYWORD = 3
28 PYGMENTS_PSEUDOKEYWORD = 4
29 PYGMENTS_TYPEKEYWORD = 5
30 PYGMENTS_OPERATOR = 6
31 PYGMENTS_WORD = 7
32 PYGMENTS_BUILTIN = 8
33 PYGMENTS_FUNCTION = 9
34 PYGMENTS_CLASS = 10
35 PYGMENTS_NAMESPACE = 11
36 PYGMENTS_EXCEPTION = 12
37 PYGMENTS_VARIABLE = 13
38 PYGMENTS_CONSTANT = 14
39 PYGMENTS_LABEL = 15
40 PYGMENTS_ENTITY = 16
41 PYGMENTS_ATTRIBUTE = 17
42 PYGMENTS_TAG = 18
43 PYGMENTS_DECORATOR = 19
44 PYGMENTS_STRING = 20
45 PYGMENTS_DOCSTRING = 21
46 PYGMENTS_SCALAR = 22
47 PYGMENTS_ESCAPE = 23
48 PYGMENTS_REGEX = 24
49 PYGMENTS_SYMBOL = 25
50 PYGMENTS_OTHER = 26
51 PYGMENTS_NUMBER = 27
52 PYGMENTS_HEADING = 28
53 PYGMENTS_SUBHEADING = 29
54 PYGMENTS_DELETED = 30
55 PYGMENTS_INSERTED = 31
56 # 32 to 39 are reserved for QScintilla internal styles
57 PYGMENTS_GENERIC_ERROR = 40
58 PYGMENTS_EMPHASIZE = 41
59 PYGMENTS_STRONG = 42
60 PYGMENTS_PROMPT = 43
61 PYGMENTS_OUTPUT = 44
62 PYGMENTS_TRACEBACK = 45
63 PYGMENTS_ERROR = 46
64 PYGMENTS_MULTILINECOMMENT = 47
65 PYGMENTS_PROPERTY = 48
66 PYGMENTS_CHAR = 49
67 PYGMENTS_HEREDOC = 50
68 PYGMENTS_PUNCTUATION = 51
69 # added with Pygments 2.1
70 PYGMENTS_HASHBANG = 52
71 PYGMENTS_RESERVEDKEYWORD = 53
72 PYGMENTS_LITERAL = 54
73 PYGMENTS_DOUBLESTRING = 55
74 PYGMENTS_SINGLESTRING = 56
75 PYGMENTS_BACKTICKSTRING = 57
76 PYGMENTS_WHITESPACE = 58
77
78 #-----------------------------------------------------------------------------#
79
80 TOKEN_MAP = {
81 Token.Comment: PYGMENTS_COMMENT,
82 Token.Comment.Hashbang: PYGMENTS_HASHBANG,
83 Token.Comment.Multiline: PYGMENTS_MULTILINECOMMENT,
84 Token.Comment.Preproc: PYGMENTS_PREPROCESSOR,
85 Token.Comment.PreprocFile: PYGMENTS_PREPROCESSOR,
86 Token.Comment.Single: PYGMENTS_COMMENT,
87 Token.Comment.Special: PYGMENTS_COMMENT,
88
89 Token.Escape: PYGMENTS_ESCAPE,
90
91 Token.Error: PYGMENTS_ERROR,
92
93 Token.Generic: PYGMENTS_DEFAULT,
94 Token.Generic.Deleted: PYGMENTS_DELETED,
95 Token.Generic.Emph: PYGMENTS_EMPHASIZE,
96 Token.Generic.Error: PYGMENTS_GENERIC_ERROR,
97 Token.Generic.Heading: PYGMENTS_HEADING,
98 Token.Generic.Inserted: PYGMENTS_INSERTED,
99 Token.Generic.Output: PYGMENTS_OUTPUT,
100 Token.Generic.Prompt: PYGMENTS_PROMPT,
101 Token.Generic.Strong: PYGMENTS_STRONG,
102 Token.Generic.Subheading: PYGMENTS_SUBHEADING,
103 Token.Generic.Traceback: PYGMENTS_TRACEBACK,
104
105 Token.Keyword: PYGMENTS_KEYWORD,
106 Token.Keyword.Constant: PYGMENTS_KEYWORD,
107 Token.Keyword.Declaration: PYGMENTS_KEYWORD,
108 Token.Keyword.Namespace: PYGMENTS_KEYWORD,
109 Token.Keyword.Pseudo: PYGMENTS_PSEUDOKEYWORD,
110 Token.Keyword.Reserved: PYGMENTS_RESERVEDKEYWORD,
111 Token.Keyword.Type: PYGMENTS_TYPEKEYWORD,
112
113 Token.Literal: PYGMENTS_LITERAL,
114 Token.Literal.Date: PYGMENTS_LITERAL,
115
116 Token.Name: PYGMENTS_DEFAULT,
117 Token.Name.Attribute: PYGMENTS_ATTRIBUTE,
118 Token.Name.Builtin: PYGMENTS_BUILTIN,
119 Token.Name.Builtin.Pseudo: PYGMENTS_BUILTIN,
120 Token.Name.Class: PYGMENTS_CLASS,
121 Token.Name.Constant: PYGMENTS_CONSTANT,
122 Token.Name.Decorator: PYGMENTS_DECORATOR,
123 Token.Name.Entity: PYGMENTS_ENTITY,
124 Token.Name.Exception: PYGMENTS_EXCEPTION,
125 Token.Name.Function: PYGMENTS_FUNCTION,
126 Token.Name.Function.Magic: PYGMENTS_FUNCTION,
127 Token.Name.Label: PYGMENTS_LABEL,
128 Token.Name.Namespace: PYGMENTS_NAMESPACE,
129 Token.Name.Other: PYGMENTS_VARIABLE,
130 Token.Name.Property: PYGMENTS_PROPERTY,
131 Token.Name.Tag: PYGMENTS_TAG,
132 Token.Name.Variable: PYGMENTS_VARIABLE,
133 Token.Name.Variable.Class: PYGMENTS_VARIABLE,
134 Token.Name.Variable.Global: PYGMENTS_VARIABLE,
135 Token.Name.Variable.Instance: PYGMENTS_VARIABLE,
136 Token.Name.Variable.Magic: PYGMENTS_VARIABLE,
137
138 Token.Number: PYGMENTS_NUMBER,
139 Token.Number.Bin: PYGMENTS_NUMBER,
140 Token.Number.Float: PYGMENTS_NUMBER,
141 Token.Number.Hex: PYGMENTS_NUMBER,
142 Token.Number.Integer: PYGMENTS_NUMBER,
143 Token.Number.Integer.Long: PYGMENTS_NUMBER,
144 Token.Number.Oct: PYGMENTS_NUMBER,
145
146 Token.Operator: PYGMENTS_OPERATOR,
147 Token.Operator.Word: PYGMENTS_WORD,
148
149 Token.Other: PYGMENTS_DEFAULT,
150
151 Token.Punctuation: PYGMENTS_PUNCTUATION,
152
153 Token.String: PYGMENTS_STRING,
154 Token.String.Affix: PYGMENTS_STRING,
155 Token.String.Backtick: PYGMENTS_BACKTICKSTRING,
156 Token.String.Char: PYGMENTS_CHAR,
157 Token.String.Delimiter: PYGMENTS_STRING,
158 Token.String.Doc: PYGMENTS_DOCSTRING,
159 Token.String.Double: PYGMENTS_DOUBLESTRING,
160 Token.String.Escape: PYGMENTS_ESCAPE,
161 Token.String.Heredoc: PYGMENTS_HEREDOC,
162 Token.String.Interpol: PYGMENTS_SCALAR,
163 Token.String.Other: PYGMENTS_OTHER,
164 Token.String.Regex: PYGMENTS_REGEX,
165 Token.String.Single: PYGMENTS_SINGLESTRING,
166 Token.String.Symbol: PYGMENTS_SYMBOL,
167
168 Token.Whitespace: PYGMENTS_WHITESPACE,
169
170 Token.Text: PYGMENTS_DEFAULT,
171 }
172
173 #-----------------------------------------------------------------------------#
174
175
176 class LexerPygments(LexerContainer):
177 """
178 Class implementing a custom lexer using pygments.
179 """
180 def __init__(self, parent=None, name=""):
181 """
182 Constructor
183
184 @param parent parent widget of this lexer
185 @param name name of the pygments lexer to use (string)
186 """
187 super().__init__(parent)
188
189 self.__inReadSettings = False
190
191 if name.startswith("Pygments|"):
192 self.__forcedPygmentsName = True
193 self.__pygmentsName = name.replace("Pygments|", "")
194 elif name:
195 self.__pygmentsName = name
196 self.__forcedPygmentsName = True
197 else:
198 self.__pygmentsName = ""
199 self.__forcedPygmentsName = False
200
201 self.descriptions = {
202 PYGMENTS_DEFAULT: self.tr("Default"),
203 PYGMENTS_COMMENT: self.tr("Comment"),
204 PYGMENTS_PREPROCESSOR: self.tr("Preprocessor"),
205 PYGMENTS_KEYWORD: self.tr("Keyword"),
206 PYGMENTS_PSEUDOKEYWORD: self.tr("Pseudo Keyword"),
207 PYGMENTS_TYPEKEYWORD: self.tr("Type Keyword"),
208 PYGMENTS_OPERATOR: self.tr("Operator"),
209 PYGMENTS_WORD: self.tr("Word"),
210 PYGMENTS_BUILTIN: self.tr("Builtin"),
211 PYGMENTS_FUNCTION: self.tr("Function or method name"),
212 PYGMENTS_CLASS: self.tr("Class name"),
213 PYGMENTS_NAMESPACE: self.tr("Namespace"),
214 PYGMENTS_EXCEPTION: self.tr("Exception"),
215 PYGMENTS_VARIABLE: self.tr("Identifier"),
216 PYGMENTS_CONSTANT: self.tr("Constant"),
217 PYGMENTS_LABEL: self.tr("Label"),
218 PYGMENTS_ENTITY: self.tr("Entity"),
219 PYGMENTS_ATTRIBUTE: self.tr("Attribute"),
220 PYGMENTS_TAG: self.tr("Tag"),
221 PYGMENTS_DECORATOR: self.tr("Decorator"),
222 PYGMENTS_STRING: self.tr("String"),
223 PYGMENTS_DOCSTRING: self.tr("Documentation string"),
224 PYGMENTS_SCALAR: self.tr("Scalar"),
225 PYGMENTS_ESCAPE: self.tr("Escape"),
226 PYGMENTS_REGEX: self.tr("Regular expression"),
227 PYGMENTS_SYMBOL: self.tr("Symbol"),
228 PYGMENTS_OTHER: self.tr("Other string"),
229 PYGMENTS_NUMBER: self.tr("Number"),
230 PYGMENTS_HEADING: self.tr("Heading"),
231 PYGMENTS_SUBHEADING: self.tr("Subheading"),
232 PYGMENTS_DELETED: self.tr("Deleted"),
233 PYGMENTS_INSERTED: self.tr("Inserted"),
234 PYGMENTS_GENERIC_ERROR: self.tr("Generic error"),
235 PYGMENTS_EMPHASIZE: self.tr("Emphasized text"),
236 PYGMENTS_STRONG: self.tr("Strong text"),
237 PYGMENTS_PROMPT: self.tr("Prompt"),
238 PYGMENTS_OUTPUT: self.tr("Output"),
239 PYGMENTS_TRACEBACK: self.tr("Traceback"),
240 PYGMENTS_ERROR: self.tr("Error"),
241 PYGMENTS_MULTILINECOMMENT: self.tr("Comment block"),
242 PYGMENTS_PROPERTY: self.tr("Property"),
243 PYGMENTS_CHAR: self.tr("Character"),
244 PYGMENTS_HEREDOC: self.tr("Here document"),
245 PYGMENTS_PUNCTUATION: self.tr("Punctuation"),
246 PYGMENTS_HASHBANG: self.tr("Hashbang"),
247 PYGMENTS_RESERVEDKEYWORD: self.tr("Reserved Keyword"),
248 PYGMENTS_LITERAL: self.tr("Literal"),
249 PYGMENTS_DOUBLESTRING: self.tr("Double quoted string"),
250 PYGMENTS_SINGLESTRING: self.tr("Single quoted string"),
251 PYGMENTS_BACKTICKSTRING: self.tr("Backtick string"),
252 PYGMENTS_WHITESPACE: self.tr("Whitespace"),
253 }
254
255 self.defaultColors = {
256 PYGMENTS_DEFAULT: QColor("#000000"),
257 PYGMENTS_COMMENT: QColor("#408080"),
258 PYGMENTS_PREPROCESSOR: QColor("#BC7A00"),
259 PYGMENTS_KEYWORD: QColor("#008000"),
260 PYGMENTS_PSEUDOKEYWORD: QColor("#008000"),
261 PYGMENTS_TYPEKEYWORD: QColor("#B00040"),
262 PYGMENTS_OPERATOR: QColor("#666666"),
263 PYGMENTS_WORD: QColor("#AA22FF"),
264 PYGMENTS_BUILTIN: QColor("#008000"),
265 PYGMENTS_FUNCTION: QColor("#0000FF"),
266 PYGMENTS_CLASS: QColor("#0000FF"),
267 PYGMENTS_NAMESPACE: QColor("#0000FF"),
268 PYGMENTS_EXCEPTION: QColor("#D2413A"),
269 PYGMENTS_VARIABLE: QColor("#19177C"),
270 PYGMENTS_CONSTANT: QColor("#880000"),
271 PYGMENTS_LABEL: QColor("#A0A000"),
272 PYGMENTS_ENTITY: QColor("#999999"),
273 PYGMENTS_ATTRIBUTE: QColor("#7D9029"),
274 PYGMENTS_TAG: QColor("#008000"),
275 PYGMENTS_DECORATOR: QColor("#AA22FF"),
276 PYGMENTS_STRING: QColor("#BA2121"),
277 PYGMENTS_DOCSTRING: QColor("#BA2121"),
278 PYGMENTS_SCALAR: QColor("#BB6688"),
279 PYGMENTS_ESCAPE: QColor("#BB6622"),
280 PYGMENTS_REGEX: QColor("#BB6688"),
281 PYGMENTS_SYMBOL: QColor("#19177C"),
282 PYGMENTS_OTHER: QColor("#008000"),
283 PYGMENTS_NUMBER: QColor("#666666"),
284 PYGMENTS_HEADING: QColor("#000080"),
285 PYGMENTS_SUBHEADING: QColor("#800080"),
286 PYGMENTS_DELETED: QColor("#A00000"),
287 PYGMENTS_INSERTED: QColor("#00A000"),
288 PYGMENTS_GENERIC_ERROR: QColor("#FF0000"),
289 PYGMENTS_PROMPT: QColor("#000080"),
290 PYGMENTS_OUTPUT: QColor("#808080"),
291 PYGMENTS_TRACEBACK: QColor("#0040D0"),
292 PYGMENTS_MULTILINECOMMENT: QColor("#007F00"),
293 PYGMENTS_PROPERTY: QColor("#00A0E0"),
294 PYGMENTS_CHAR: QColor("#7F007F"),
295 PYGMENTS_HEREDOC: QColor("#7F007F"),
296 PYGMENTS_PUNCTUATION: QColor("#000000"),
297 PYGMENTS_HASHBANG: QColor("#00C000"),
298 PYGMENTS_RESERVEDKEYWORD: QColor("#A90D91"),
299 PYGMENTS_LITERAL: QColor("#1C01CE"),
300 PYGMENTS_DOUBLESTRING: QColor("#7F007F"),
301 PYGMENTS_SINGLESTRING: QColor("#7F007F"),
302 PYGMENTS_BACKTICKSTRING: QColor("#FFFF00"),
303 PYGMENTS_WHITESPACE: QColor("#BBBBBB"),
304 }
305
306 self.defaultPapers = {
307 PYGMENTS_ERROR: QColor("#FF0000"),
308 PYGMENTS_MULTILINECOMMENT: QColor("#A8FFA8"),
309 PYGMENTS_HEREDOC: QColor("#DDD0DD"),
310 PYGMENTS_BACKTICKSTRING: QColor("#a08080"),
311 }
312
313 self.defaultEolFills = {
314 PYGMENTS_ERROR: True,
315 PYGMENTS_MULTILINECOMMENT: True,
316 PYGMENTS_HEREDOC: True,
317 PYGMENTS_BACKTICKSTRING: True,
318 }
319
320 def readSettings(self, qs, prefix="/Scintilla"):
321 """
322 Public method to read the lexer settings.
323
324 Note: Overridden to treat the Pygments lexer specially.
325
326 @param qs reference to the settings object
327 @type QSettings
328 @param prefix prefix for the settings key (defaults to "/Scintilla")
329 @type str (optional)
330 """
331 self.__inReadSettings = True
332 super().readSettings(qs, prefix=prefix)
333 self.__inReadSettings = False
334
335 def language(self):
336 """
337 Public method returning the language of the lexer.
338
339 @return language of the lexer (string)
340 """
341 if self.__pygmentsName and not self.__inReadSettings:
342 return self.__pygmentsName
343 else:
344 return "Guessed"
345
346 def description(self, style):
347 """
348 Public method returning the descriptions of the styles supported
349 by the lexer.
350
351 @param style style number (integer)
352 @return description for the style (string)
353 """
354 try:
355 return self.descriptions[style]
356 except KeyError:
357 return ""
358
359 def defaultColor(self, style):
360 """
361 Public method to get the default foreground color for a style.
362
363 @param style style number (integer)
364 @return foreground color (QColor)
365 """
366 try:
367 return self.defaultColors[style]
368 except KeyError:
369 return LexerContainer.defaultColor(self, style)
370
371 def defaultPaper(self, style):
372 """
373 Public method to get the default background color for a style.
374
375 @param style style number (integer)
376 @return background color (QColor)
377 """
378 try:
379 return self.defaultPapers[style]
380 except KeyError:
381 return LexerContainer.defaultPaper(self, style)
382
383 def defaultFont(self, style):
384 """
385 Public method to get the default font for a style.
386
387 @param style style number (integer)
388 @return font (QFont)
389 """
390 if style in [PYGMENTS_COMMENT, PYGMENTS_PREPROCESSOR,
391 PYGMENTS_MULTILINECOMMENT]:
392 if Utilities.isWindowsPlatform():
393 f = QFont(["Comic Sans MS"], 9)
394 elif Utilities.isMacPlatform():
395 f = QFont(["Courier"], 11)
396 else:
397 f = QFont(["Bitstream Vera Serif"], 9)
398 if style == PYGMENTS_PREPROCESSOR:
399 f.setItalic(True)
400 return f
401
402 if style in [PYGMENTS_STRING, PYGMENTS_CHAR]:
403 if Utilities.isWindowsPlatform():
404 return QFont(["Comic Sans MS"], 10)
405 elif Utilities.isMacPlatform():
406 f = QFont(["Courier"], 11)
407 else:
408 return QFont(["Bitstream Vera Serif"], 10)
409
410 if style in [PYGMENTS_KEYWORD, PYGMENTS_OPERATOR, PYGMENTS_WORD,
411 PYGMENTS_BUILTIN, PYGMENTS_ATTRIBUTE, PYGMENTS_FUNCTION,
412 PYGMENTS_CLASS, PYGMENTS_NAMESPACE, PYGMENTS_EXCEPTION,
413 PYGMENTS_ENTITY, PYGMENTS_TAG, PYGMENTS_SCALAR,
414 PYGMENTS_ESCAPE, PYGMENTS_HEADING, PYGMENTS_SUBHEADING,
415 PYGMENTS_STRONG, PYGMENTS_PROMPT]:
416 f = LexerContainer.defaultFont(self, style)
417 f.setBold(True)
418 return f
419
420 if style in [PYGMENTS_DOCSTRING, PYGMENTS_EMPHASIZE]:
421 f = LexerContainer.defaultFont(self, style)
422 f.setItalic(True)
423 return f
424
425 return LexerContainer.defaultFont(self, style)
426
427 def defaultEolFill(self, style):
428 """
429 Public method to get the default fill to eol flag.
430
431 @param style style number (integer)
432 @return fill to eol flag (boolean)
433 """
434 try:
435 return self.defaultEolFills[style]
436 except KeyError:
437 return LexerContainer.defaultEolFill(self, style)
438
439 def __guessLexer(self, text):
440 """
441 Private method to guess a pygments lexer.
442
443 @param text text to base guessing on (string)
444 @return reference to the guessed lexer (pygments.lexer)
445 """
446 lexer = None
447
448 if self.__pygmentsName:
449 lexerClass = find_lexer_class(self.__pygmentsName)
450 if lexerClass is not None:
451 lexer = lexerClass()
452
453 elif text:
454 # step 1: guess based on filename and text
455 if self.editor is not None:
456 fn = self.editor.getFileName()
457 if fn:
458 with contextlib.suppress(ClassNotFound, AttributeError):
459 lexer = guess_lexer_for_filename(fn, text)
460
461 # step 2: guess on text only
462 if lexer is None:
463 with contextlib.suppress(ClassNotFound, AttributeError):
464 lexer = guess_lexer(text)
465
466 return lexer
467
468 def canStyle(self):
469 """
470 Public method to check, if the lexer is able to style the text.
471
472 @return flag indicating the lexer capability (boolean)
473 """
474 if self.editor is None:
475 return True
476
477 text = self.editor.text()
478 self.__lexer = self.__guessLexer(text)
479
480 return self.__lexer is not None
481
482 def name(self):
483 """
484 Public method to get the name of the pygments lexer.
485
486 @return name of the pygments lexer (string)
487 """
488 if self.__lexer is None:
489 return ""
490 else:
491 return self.__lexer.name
492
493 def styleText(self, start, end):
494 """
495 Public method to perform the styling.
496
497 @param start position of first character to be styled (integer)
498 @param end position of last character to be styled (integer)
499 """
500 text = self.editor.text()[:end + 1]
501 textLen = len(text.encode("utf-8"))
502 self.__lexer = self.__guessLexer(text)
503
504 cpos = 0
505 # adjust start position because pygments ignores empty line at
506 # start of text
507 for c in text:
508 if c == "\n":
509 cpos += 1
510 else:
511 break
512
513 self.editor.startStyling(cpos, 0x3f)
514 if self.__lexer is None:
515 self.editor.setStyling(len(text), PYGMENTS_DEFAULT)
516 else:
517 eolLen = len(self.editor.getLineSeparator())
518 for token, txt in self.__lexer.get_tokens(text):
519 style = TOKEN_MAP.get(token, PYGMENTS_DEFAULT)
520
521 tlen = len(txt.encode('utf-8'))
522 if eolLen > 1:
523 tlen += txt.count('\n')
524 cpos += tlen
525 if tlen and cpos < textLen:
526 self.editor.setStyling(tlen, style)
527 if cpos >= textLen:
528 break
529 self.editor.startStyling(cpos, 0x3f)
530
531 def isCommentStyle(self, style):
532 """
533 Public method to check, if a style is a comment style.
534
535 @param style style to check (integer)
536 @return flag indicating a comment style (boolean)
537 """
538 return style in [PYGMENTS_COMMENT]
539
540 def isStringStyle(self, style):
541 """
542 Public method to check, if a style is a string style.
543
544 @param style style to check (integer)
545 @return flag indicating a string style (boolean)
546 """
547 return style in [PYGMENTS_STRING, PYGMENTS_DOCSTRING, PYGMENTS_OTHER,
548 PYGMENTS_HEADING, PYGMENTS_SUBHEADING,
549 PYGMENTS_EMPHASIZE, PYGMENTS_STRONG]
550
551 def defaultKeywords(self, kwSet):
552 """
553 Public method to get the default keywords.
554
555 @param kwSet number of the keyword set (integer)
556 @return string giving the keywords (string) or None
557 """
558 return None # __IGNORE_WARNING_M831__

eric ide

mercurial