162 Token.String.Interpol: PYGMENTS_SCALAR, |
149 Token.String.Interpol: PYGMENTS_SCALAR, |
163 Token.String.Other: PYGMENTS_OTHER, |
150 Token.String.Other: PYGMENTS_OTHER, |
164 Token.String.Regex: PYGMENTS_REGEX, |
151 Token.String.Regex: PYGMENTS_REGEX, |
165 Token.String.Single: PYGMENTS_SINGLESTRING, |
152 Token.String.Single: PYGMENTS_SINGLESTRING, |
166 Token.String.Symbol: PYGMENTS_SYMBOL, |
153 Token.String.Symbol: PYGMENTS_SYMBOL, |
167 |
|
168 Token.Whitespace: PYGMENTS_WHITESPACE, |
154 Token.Whitespace: PYGMENTS_WHITESPACE, |
169 |
|
170 Token.Text: PYGMENTS_DEFAULT, |
155 Token.Text: PYGMENTS_DEFAULT, |
171 } |
156 } |
172 |
157 |
173 #-----------------------------------------------------------------------------# |
158 # -----------------------------------------------------------------------------# |
174 |
159 |
175 |
160 |
176 class LexerPygments(LexerContainer): |
161 class LexerPygments(LexerContainer): |
177 """ |
162 """ |
178 Class implementing a custom lexer using pygments. |
163 Class implementing a custom lexer using pygments. |
179 """ |
164 """ |
|
165 |
180 def __init__(self, parent=None, name=""): |
166 def __init__(self, parent=None, name=""): |
181 """ |
167 """ |
182 Constructor |
168 Constructor |
183 |
169 |
184 @param parent parent widget of this lexer |
170 @param parent parent widget of this lexer |
185 @param name name of the pygments lexer to use (string) |
171 @param name name of the pygments lexer to use (string) |
186 """ |
172 """ |
187 super().__init__(parent) |
173 super().__init__(parent) |
188 |
174 |
189 self.__inReadSettings = False |
175 self.__inReadSettings = False |
190 |
176 |
191 if name.startswith("Pygments|"): |
177 if name.startswith("Pygments|"): |
192 self.__forcedPygmentsName = True |
178 self.__forcedPygmentsName = True |
193 self.__pygmentsName = name.replace("Pygments|", "") |
179 self.__pygmentsName = name.replace("Pygments|", "") |
194 elif name: |
180 elif name: |
195 self.__pygmentsName = name |
181 self.__pygmentsName = name |
196 self.__forcedPygmentsName = True |
182 self.__forcedPygmentsName = True |
197 else: |
183 else: |
198 self.__pygmentsName = "" |
184 self.__pygmentsName = "" |
199 self.__forcedPygmentsName = False |
185 self.__forcedPygmentsName = False |
200 |
186 |
201 self.descriptions = { |
187 self.descriptions = { |
202 PYGMENTS_DEFAULT: self.tr("Default"), |
188 PYGMENTS_DEFAULT: self.tr("Default"), |
203 PYGMENTS_COMMENT: self.tr("Comment"), |
189 PYGMENTS_COMMENT: self.tr("Comment"), |
204 PYGMENTS_PREPROCESSOR: self.tr("Preprocessor"), |
190 PYGMENTS_PREPROCESSOR: self.tr("Preprocessor"), |
205 PYGMENTS_KEYWORD: self.tr("Keyword"), |
191 PYGMENTS_KEYWORD: self.tr("Keyword"), |
300 PYGMENTS_DOUBLESTRING: QColor("#7F007F"), |
286 PYGMENTS_DOUBLESTRING: QColor("#7F007F"), |
301 PYGMENTS_SINGLESTRING: QColor("#7F007F"), |
287 PYGMENTS_SINGLESTRING: QColor("#7F007F"), |
302 PYGMENTS_BACKTICKSTRING: QColor("#FFFF00"), |
288 PYGMENTS_BACKTICKSTRING: QColor("#FFFF00"), |
303 PYGMENTS_WHITESPACE: QColor("#BBBBBB"), |
289 PYGMENTS_WHITESPACE: QColor("#BBBBBB"), |
304 } |
290 } |
305 |
291 |
306 self.defaultPapers = { |
292 self.defaultPapers = { |
307 PYGMENTS_ERROR: QColor("#FF0000"), |
293 PYGMENTS_ERROR: QColor("#FF0000"), |
308 PYGMENTS_MULTILINECOMMENT: QColor("#A8FFA8"), |
294 PYGMENTS_MULTILINECOMMENT: QColor("#A8FFA8"), |
309 PYGMENTS_HEREDOC: QColor("#DDD0DD"), |
295 PYGMENTS_HEREDOC: QColor("#DDD0DD"), |
310 PYGMENTS_BACKTICKSTRING: QColor("#a08080"), |
296 PYGMENTS_BACKTICKSTRING: QColor("#a08080"), |
311 } |
297 } |
312 |
298 |
313 self.defaultEolFills = { |
299 self.defaultEolFills = { |
314 PYGMENTS_ERROR: True, |
300 PYGMENTS_ERROR: True, |
315 PYGMENTS_MULTILINECOMMENT: True, |
301 PYGMENTS_MULTILINECOMMENT: True, |
316 PYGMENTS_HEREDOC: True, |
302 PYGMENTS_HEREDOC: True, |
317 PYGMENTS_BACKTICKSTRING: True, |
303 PYGMENTS_BACKTICKSTRING: True, |
318 } |
304 } |
319 |
305 |
320 def readSettings(self, qs, prefix="/Scintilla"): |
306 def readSettings(self, qs, prefix="/Scintilla"): |
321 """ |
307 """ |
322 Public method to read the lexer settings. |
308 Public method to read the lexer settings. |
323 |
309 |
324 Note: Overridden to treat the Pygments lexer specially. |
310 Note: Overridden to treat the Pygments lexer specially. |
325 |
311 |
326 @param qs reference to the settings object |
312 @param qs reference to the settings object |
327 @type QSettings |
313 @type QSettings |
328 @param prefix prefix for the settings key (defaults to "/Scintilla") |
314 @param prefix prefix for the settings key (defaults to "/Scintilla") |
329 @type str (optional) |
315 @type str (optional) |
330 """ |
316 """ |
331 self.__inReadSettings = True |
317 self.__inReadSettings = True |
332 super().readSettings(qs, prefix=prefix) |
318 super().readSettings(qs, prefix=prefix) |
333 self.__inReadSettings = False |
319 self.__inReadSettings = False |
334 |
320 |
335 def language(self): |
321 def language(self): |
336 """ |
322 """ |
337 Public method returning the language of the lexer. |
323 Public method returning the language of the lexer. |
338 |
324 |
339 @return language of the lexer (string) |
325 @return language of the lexer (string) |
340 """ |
326 """ |
341 if self.__pygmentsName and not self.__inReadSettings: |
327 if self.__pygmentsName and not self.__inReadSettings: |
342 return self.__pygmentsName |
328 return self.__pygmentsName |
343 else: |
329 else: |
344 return "Guessed" |
330 return "Guessed" |
345 |
331 |
346 def description(self, style): |
332 def description(self, style): |
347 """ |
333 """ |
348 Public method returning the descriptions of the styles supported |
334 Public method returning the descriptions of the styles supported |
349 by the lexer. |
335 by the lexer. |
350 |
336 |
351 @param style style number (integer) |
337 @param style style number (integer) |
352 @return description for the style (string) |
338 @return description for the style (string) |
353 """ |
339 """ |
354 try: |
340 try: |
355 return self.descriptions[style] |
341 return self.descriptions[style] |
356 except KeyError: |
342 except KeyError: |
357 return "" |
343 return "" |
358 |
344 |
359 def defaultColor(self, style): |
345 def defaultColor(self, style): |
360 """ |
346 """ |
361 Public method to get the default foreground color for a style. |
347 Public method to get the default foreground color for a style. |
362 |
348 |
363 @param style style number (integer) |
349 @param style style number (integer) |
364 @return foreground color (QColor) |
350 @return foreground color (QColor) |
365 """ |
351 """ |
366 try: |
352 try: |
367 return self.defaultColors[style] |
353 return self.defaultColors[style] |
368 except KeyError: |
354 except KeyError: |
369 return LexerContainer.defaultColor(self, style) |
355 return LexerContainer.defaultColor(self, style) |
370 |
356 |
371 def defaultPaper(self, style): |
357 def defaultPaper(self, style): |
372 """ |
358 """ |
373 Public method to get the default background color for a style. |
359 Public method to get the default background color for a style. |
374 |
360 |
375 @param style style number (integer) |
361 @param style style number (integer) |
376 @return background color (QColor) |
362 @return background color (QColor) |
377 """ |
363 """ |
378 try: |
364 try: |
379 return self.defaultPapers[style] |
365 return self.defaultPapers[style] |
380 except KeyError: |
366 except KeyError: |
381 return LexerContainer.defaultPaper(self, style) |
367 return LexerContainer.defaultPaper(self, style) |
382 |
368 |
383 def defaultFont(self, style): |
369 def defaultFont(self, style): |
384 """ |
370 """ |
385 Public method to get the default font for a style. |
371 Public method to get the default font for a style. |
386 |
372 |
387 @param style style number (integer) |
373 @param style style number (integer) |
388 @return font (QFont) |
374 @return font (QFont) |
389 """ |
375 """ |
390 if style in [PYGMENTS_COMMENT, PYGMENTS_PREPROCESSOR, |
376 if style in [ |
391 PYGMENTS_MULTILINECOMMENT]: |
377 PYGMENTS_COMMENT, |
|
378 PYGMENTS_PREPROCESSOR, |
|
379 PYGMENTS_MULTILINECOMMENT, |
|
380 ]: |
392 if Utilities.isWindowsPlatform(): |
381 if Utilities.isWindowsPlatform(): |
393 f = QFont(["Comic Sans MS"], 9) |
382 f = QFont(["Comic Sans MS"], 9) |
394 elif Utilities.isMacPlatform(): |
383 elif Utilities.isMacPlatform(): |
395 f = QFont(["Courier"], 11) |
384 f = QFont(["Courier"], 11) |
396 else: |
385 else: |
397 f = QFont(["Bitstream Vera Serif"], 9) |
386 f = QFont(["Bitstream Vera Serif"], 9) |
398 if style == PYGMENTS_PREPROCESSOR: |
387 if style == PYGMENTS_PREPROCESSOR: |
399 f.setItalic(True) |
388 f.setItalic(True) |
400 return f |
389 return f |
401 |
390 |
402 if style in [PYGMENTS_STRING, PYGMENTS_CHAR]: |
391 if style in [PYGMENTS_STRING, PYGMENTS_CHAR]: |
403 if Utilities.isWindowsPlatform(): |
392 if Utilities.isWindowsPlatform(): |
404 return QFont(["Comic Sans MS"], 10) |
393 return QFont(["Comic Sans MS"], 10) |
405 elif Utilities.isMacPlatform(): |
394 elif Utilities.isMacPlatform(): |
406 f = QFont(["Courier"], 11) |
395 f = QFont(["Courier"], 11) |
407 else: |
396 else: |
408 return QFont(["Bitstream Vera Serif"], 10) |
397 return QFont(["Bitstream Vera Serif"], 10) |
409 |
398 |
410 if style in [PYGMENTS_KEYWORD, PYGMENTS_OPERATOR, PYGMENTS_WORD, |
399 if style in [ |
411 PYGMENTS_BUILTIN, PYGMENTS_ATTRIBUTE, PYGMENTS_FUNCTION, |
400 PYGMENTS_KEYWORD, |
412 PYGMENTS_CLASS, PYGMENTS_NAMESPACE, PYGMENTS_EXCEPTION, |
401 PYGMENTS_OPERATOR, |
413 PYGMENTS_ENTITY, PYGMENTS_TAG, PYGMENTS_SCALAR, |
402 PYGMENTS_WORD, |
414 PYGMENTS_ESCAPE, PYGMENTS_HEADING, PYGMENTS_SUBHEADING, |
403 PYGMENTS_BUILTIN, |
415 PYGMENTS_STRONG, PYGMENTS_PROMPT]: |
404 PYGMENTS_ATTRIBUTE, |
|
405 PYGMENTS_FUNCTION, |
|
406 PYGMENTS_CLASS, |
|
407 PYGMENTS_NAMESPACE, |
|
408 PYGMENTS_EXCEPTION, |
|
409 PYGMENTS_ENTITY, |
|
410 PYGMENTS_TAG, |
|
411 PYGMENTS_SCALAR, |
|
412 PYGMENTS_ESCAPE, |
|
413 PYGMENTS_HEADING, |
|
414 PYGMENTS_SUBHEADING, |
|
415 PYGMENTS_STRONG, |
|
416 PYGMENTS_PROMPT, |
|
417 ]: |
416 f = LexerContainer.defaultFont(self, style) |
418 f = LexerContainer.defaultFont(self, style) |
417 f.setBold(True) |
419 f.setBold(True) |
418 return f |
420 return f |
419 |
421 |
420 if style in [PYGMENTS_DOCSTRING, PYGMENTS_EMPHASIZE]: |
422 if style in [PYGMENTS_DOCSTRING, PYGMENTS_EMPHASIZE]: |
421 f = LexerContainer.defaultFont(self, style) |
423 f = LexerContainer.defaultFont(self, style) |
422 f.setItalic(True) |
424 f.setItalic(True) |
423 return f |
425 return f |
424 |
426 |
425 return LexerContainer.defaultFont(self, style) |
427 return LexerContainer.defaultFont(self, style) |
426 |
428 |
427 def defaultEolFill(self, style): |
429 def defaultEolFill(self, style): |
428 """ |
430 """ |
429 Public method to get the default fill to eol flag. |
431 Public method to get the default fill to eol flag. |
430 |
432 |
431 @param style style number (integer) |
433 @param style style number (integer) |
432 @return fill to eol flag (boolean) |
434 @return fill to eol flag (boolean) |
433 """ |
435 """ |
434 try: |
436 try: |
435 return self.defaultEolFills[style] |
437 return self.defaultEolFills[style] |
436 except KeyError: |
438 except KeyError: |
437 return LexerContainer.defaultEolFill(self, style) |
439 return LexerContainer.defaultEolFill(self, style) |
438 |
440 |
439 def __guessLexer(self, text): |
441 def __guessLexer(self, text): |
440 """ |
442 """ |
441 Private method to guess a pygments lexer. |
443 Private method to guess a pygments lexer. |
442 |
444 |
443 @param text text to base guessing on (string) |
445 @param text text to base guessing on (string) |
444 @return reference to the guessed lexer (pygments.lexer) |
446 @return reference to the guessed lexer (pygments.lexer) |
445 """ |
447 """ |
446 lexer = None |
448 lexer = None |
447 |
449 |
448 if self.__pygmentsName: |
450 if self.__pygmentsName: |
449 lexerClass = find_lexer_class(self.__pygmentsName) |
451 lexerClass = find_lexer_class(self.__pygmentsName) |
450 if lexerClass is not None: |
452 if lexerClass is not None: |
451 lexer = lexerClass() |
453 lexer = lexerClass() |
452 |
454 |
453 elif text: |
455 elif text: |
454 # step 1: guess based on filename and text |
456 # step 1: guess based on filename and text |
455 if self.editor is not None: |
457 if self.editor is not None: |
456 fn = self.editor.getFileName() |
458 fn = self.editor.getFileName() |
457 if fn: |
459 if fn: |
458 with contextlib.suppress(ClassNotFound, AttributeError): |
460 with contextlib.suppress(ClassNotFound, AttributeError): |
459 lexer = guess_lexer_for_filename(fn, text) |
461 lexer = guess_lexer_for_filename(fn, text) |
460 |
462 |
461 # step 2: guess on text only |
463 # step 2: guess on text only |
462 if lexer is None: |
464 if lexer is None: |
463 with contextlib.suppress(ClassNotFound, AttributeError): |
465 with contextlib.suppress(ClassNotFound, AttributeError): |
464 lexer = guess_lexer(text) |
466 lexer = guess_lexer(text) |
465 |
467 |
466 return lexer |
468 return lexer |
467 |
469 |
468 def canStyle(self): |
470 def canStyle(self): |
469 """ |
471 """ |
470 Public method to check, if the lexer is able to style the text. |
472 Public method to check, if the lexer is able to style the text. |
471 |
473 |
472 @return flag indicating the lexer capability (boolean) |
474 @return flag indicating the lexer capability (boolean) |
473 """ |
475 """ |
474 if self.editor is None: |
476 if self.editor is None: |
475 return True |
477 return True |
476 |
478 |
477 text = self.editor.text() |
479 text = self.editor.text() |
478 self.__lexer = self.__guessLexer(text) |
480 self.__lexer = self.__guessLexer(text) |
479 |
481 |
480 return self.__lexer is not None |
482 return self.__lexer is not None |
481 |
483 |
482 def name(self): |
484 def name(self): |
483 """ |
485 """ |
484 Public method to get the name of the pygments lexer. |
486 Public method to get the name of the pygments lexer. |
485 |
487 |
486 @return name of the pygments lexer (string) |
488 @return name of the pygments lexer (string) |
487 """ |
489 """ |
488 if self.__lexer is None: |
490 if self.__lexer is None: |
489 return "" |
491 return "" |
490 else: |
492 else: |
491 return self.__lexer.name |
493 return self.__lexer.name |
492 |
494 |
493 def styleText(self, start, end): |
495 def styleText(self, start, end): |
494 """ |
496 """ |
495 Public method to perform the styling. |
497 Public method to perform the styling. |
496 |
498 |
497 @param start position of first character to be styled (integer) |
499 @param start position of first character to be styled (integer) |
498 @param end position of last character to be styled (integer) |
500 @param end position of last character to be styled (integer) |
499 """ |
501 """ |
500 text = self.editor.text()[:end + 1] |
502 text = self.editor.text()[: end + 1] |
501 textLen = len(text.encode("utf-8")) |
503 textLen = len(text.encode("utf-8")) |
502 self.__lexer = self.__guessLexer(text) |
504 self.__lexer = self.__guessLexer(text) |
503 |
505 |
504 cpos = 0 |
506 cpos = 0 |
505 # adjust start position because pygments ignores empty line at |
507 # adjust start position because pygments ignores empty line at |
506 # start of text |
508 # start of text |
507 for c in text: |
509 for c in text: |
508 if c == "\n": |
510 if c == "\n": |
509 cpos += 1 |
511 cpos += 1 |
510 else: |
512 else: |
511 break |
513 break |
512 |
514 |
513 self.editor.startStyling(cpos, 0x3f) |
515 self.editor.startStyling(cpos, 0x3F) |
514 if self.__lexer is None: |
516 if self.__lexer is None: |
515 self.editor.setStyling(len(text), PYGMENTS_DEFAULT) |
517 self.editor.setStyling(len(text), PYGMENTS_DEFAULT) |
516 else: |
518 else: |
517 eolLen = len(self.editor.getLineSeparator()) |
519 eolLen = len(self.editor.getLineSeparator()) |
518 for token, txt in self.__lexer.get_tokens(text): |
520 for token, txt in self.__lexer.get_tokens(text): |
519 style = TOKEN_MAP.get(token, PYGMENTS_DEFAULT) |
521 style = TOKEN_MAP.get(token, PYGMENTS_DEFAULT) |
520 |
522 |
521 tlen = len(txt.encode('utf-8')) |
523 tlen = len(txt.encode("utf-8")) |
522 if eolLen > 1: |
524 if eolLen > 1: |
523 tlen += txt.count('\n') |
525 tlen += txt.count("\n") |
524 cpos += tlen |
526 cpos += tlen |
525 if tlen and cpos < textLen: |
527 if tlen and cpos < textLen: |
526 self.editor.setStyling(tlen, style) |
528 self.editor.setStyling(tlen, style) |
527 if cpos >= textLen: |
529 if cpos >= textLen: |
528 break |
530 break |
529 self.editor.startStyling(cpos, 0x3f) |
531 self.editor.startStyling(cpos, 0x3F) |
530 |
532 |
531 def isCommentStyle(self, style): |
533 def isCommentStyle(self, style): |
532 """ |
534 """ |
533 Public method to check, if a style is a comment style. |
535 Public method to check, if a style is a comment style. |
534 |
536 |
535 @param style style to check (integer) |
537 @param style style to check (integer) |
536 @return flag indicating a comment style (boolean) |
538 @return flag indicating a comment style (boolean) |
537 """ |
539 """ |
538 return style in [PYGMENTS_COMMENT] |
540 return style in [PYGMENTS_COMMENT] |
539 |
541 |
540 def isStringStyle(self, style): |
542 def isStringStyle(self, style): |
541 """ |
543 """ |
542 Public method to check, if a style is a string style. |
544 Public method to check, if a style is a string style. |
543 |
545 |
544 @param style style to check (integer) |
546 @param style style to check (integer) |
545 @return flag indicating a string style (boolean) |
547 @return flag indicating a string style (boolean) |
546 """ |
548 """ |
547 return style in [PYGMENTS_STRING, PYGMENTS_DOCSTRING, PYGMENTS_OTHER, |
549 return style in [ |
548 PYGMENTS_HEADING, PYGMENTS_SUBHEADING, |
550 PYGMENTS_STRING, |
549 PYGMENTS_EMPHASIZE, PYGMENTS_STRONG] |
551 PYGMENTS_DOCSTRING, |
550 |
552 PYGMENTS_OTHER, |
|
553 PYGMENTS_HEADING, |
|
554 PYGMENTS_SUBHEADING, |
|
555 PYGMENTS_EMPHASIZE, |
|
556 PYGMENTS_STRONG, |
|
557 ] |
|
558 |
551 def defaultKeywords(self, kwSet): |
559 def defaultKeywords(self, kwSet): |
552 """ |
560 """ |
553 Public method to get the default keywords. |
561 Public method to get the default keywords. |
554 |
562 |
555 @param kwSet number of the keyword set (integer) |
563 @param kwSet number of the keyword set (integer) |
556 @return string giving the keywords (string) or None |
564 @return string giving the keywords (string) or None |
557 """ |
565 """ |
558 return None # __IGNORE_WARNING_M831__ |
566 return None # __IGNORE_WARNING_M831__ |