class PygmentsHighlighter(QtGui.QSyntaxHighlighter): """ Syntax highlighter that uses Pygments for parsing. """ #--------------------------------------------------------------------------- # 'QSyntaxHighlighter' interface #--------------------------------------------------------------------------- def __init__(self, parent, lexer=None): super(PygmentsHighlighter, self).__init__(parent) self._document = QtGui.QTextDocument() self._formatter = HtmlFormatter(nowrap=True) self._lexer = lexer if lexer else PythonLexer() self.set_style('default') def highlightBlock(self, string): """ Highlight a block of text. """ prev_data = self.currentBlock().previous().userData() if prev_data is not None: self._lexer._saved_state_stack = prev_data.syntax_stack elif hasattr(self._lexer, '_saved_state_stack'): del self._lexer._saved_state_stack # Lex the text using Pygments index = 0 for token, text in self._lexer.get_tokens(string): length = len(text) self.setFormat(index, length, self._get_format(token)) index += length if hasattr(self._lexer, '_saved_state_stack'): data = PygmentsBlockUserData( syntax_stack=self._lexer._saved_state_stack) self.currentBlock().setUserData(data) # Clean up for the next go-round. del self._lexer._saved_state_stack #--------------------------------------------------------------------------- # 'PygmentsHighlighter' interface #--------------------------------------------------------------------------- def set_style(self, style): """ Sets the style to the specified Pygments style. """ if isinstance(style, str): style = get_style_by_name(style) self._style = style self._clear_caches() def set_style_sheet(self, stylesheet): """ Sets a CSS stylesheet. The classes in the stylesheet should correspond to those generated by: pygmentize -S <style> -f html Note that 'set_style' and 'set_style_sheet' completely override each other, i.e. they cannot be used in conjunction. """ self._document.setDefaultStyleSheet(stylesheet) self._style = None self._clear_caches() #--------------------------------------------------------------------------- # Protected interface #--------------------------------------------------------------------------- def _clear_caches(self): """ Clear caches for brushes and formats. """ self._brushes = {} self._formats = {} def _get_format(self, token): """ Returns a QTextCharFormat for token or None. """ if token in self._formats: return self._formats[token] if self._style is None: result = self._get_format_from_document(token, self._document) else: result = self._get_format_from_style(token, self._style) self._formats[token] = result return result def _get_format_from_document(self, token, document): """ Returns a QTextCharFormat for token by """ code, html = next(self._formatter._format_lines([(token, 'dummy')])) self._document.setHtml(html) return QtGui.QTextCursor(self._document).charFormat() def _get_format_from_style(self, token, style): """ Returns a QTextCharFormat for token by reading a Pygments style. """ result = QtGui.QTextCharFormat() for key, value in list(style.style_for_token(token).items()): if value: if key == 'color': result.setForeground(self._get_brush(value)) elif key == 'bgcolor': result.setBackground(self._get_brush(value)) elif key == 'bold': result.setFontWeight(QtGui.QFont.Bold) elif key == 'italic': result.setFontItalic(True) elif key == 'underline': result.setUnderlineStyle( QtGui.QTextCharFormat.SingleUnderline) elif key == 'sans': result.setFontStyleHint(QtGui.QFont.SansSerif) elif key == 'roman': result.setFontStyleHint(QtGui.QFont.Times) elif key == 'mono': result.setFontStyleHint(QtGui.QFont.TypeWriter) return result def _get_brush(self, color): """ Returns a brush for the color. """ result = self._brushes.get(color) if result is None: qcolor = self._get_color(color) result = QtGui.QBrush(qcolor) self._brushes[color] = result return result def _get_color(self, color): """ Returns a QColor built from a Pygments color string. """ qcolor = QtGui.QColor() qcolor.setRgb(int(color[:2], base=16), int(color[2:4], base=16), int(color[4:6], base=16)) return qcolor
class PygmentsHighlighter(QtGui.QSyntaxHighlighter): """ Syntax highlighter that uses Pygments for parsing. """ #--------------------------------------------------------------------------- # 'QSyntaxHighlighter' interface #--------------------------------------------------------------------------- def __init__(self, parent, lexer=None): super(PygmentsHighlighter, self).__init__(parent) self._document = self.document() self._formatter = HtmlFormatter(nowrap=True) self.set_style('default') if lexer is not None: self._lexer = lexer else: if PY3: self._lexer = Python3Lexer() else: self._lexer = PythonLexer() def highlightBlock(self, string): """ Highlight a block of text. """ prev_data = self.currentBlock().previous().userData() if prev_data is not None: self._lexer._saved_state_stack = prev_data.syntax_stack elif hasattr(self._lexer, '_saved_state_stack'): del self._lexer._saved_state_stack # Lex the text using Pygments index = 0 for token, text in self._lexer.get_tokens(string): length = qstring_length(text) self.setFormat(index, length, self._get_format(token)) index += length if hasattr(self._lexer, '_saved_state_stack'): data = PygmentsBlockUserData( syntax_stack=self._lexer._saved_state_stack) self.currentBlock().setUserData(data) # Clean up for the next go-round. del self._lexer._saved_state_stack #--------------------------------------------------------------------------- # 'PygmentsHighlighter' interface #--------------------------------------------------------------------------- def set_style(self, style): """ Sets the style to the specified Pygments style. """ if isinstance(style, string_types): style = get_style_by_name(style) self._style = style self._clear_caches() def set_style_sheet(self, stylesheet): """ Sets a CSS stylesheet. The classes in the stylesheet should correspond to those generated by: pygmentize -S <style> -f html Note that 'set_style' and 'set_style_sheet' completely override each other, i.e. they cannot be used in conjunction. """ self._document.setDefaultStyleSheet(stylesheet) self._style = None self._clear_caches() #--------------------------------------------------------------------------- # Protected interface #--------------------------------------------------------------------------- def _clear_caches(self): """ Clear caches for brushes and formats. """ self._brushes = {} self._formats = {} def _get_format(self, token): """ Returns a QTextCharFormat for token or None. """ if token in self._formats: return self._formats[token] if self._style is None: result = self._get_format_from_document(token, self._document) else: result = self._get_format_from_style(token, self._style) self._formats[token] = result return result def _get_format_from_document(self, token, document): """ Returns a QTextCharFormat for token by """ code, html = next(self._formatter._format_lines([(token, u'dummy')])) self._document.setHtml(html) return QtGui.QTextCursor(self._document).charFormat() def _get_format_from_style(self, token, style): """ Returns a QTextCharFormat for token by reading a Pygments style. """ result = QtGui.QTextCharFormat() for key, value in style.style_for_token(token).items(): if value: if key == 'color': result.setForeground(self._get_brush(value)) elif key == 'bgcolor': result.setBackground(self._get_brush(value)) elif key == 'bold': result.setFontWeight(QtGui.QFont.Bold) elif key == 'italic': result.setFontItalic(True) elif key == 'underline': result.setUnderlineStyle( QtGui.QTextCharFormat.SingleUnderline) elif key == 'sans': result.setFontStyleHint(QtGui.QFont.SansSerif) elif key == 'roman': result.setFontStyleHint(QtGui.QFont.Times) elif key == 'mono': result.setFontStyleHint(QtGui.QFont.TypeWriter) return result def _get_brush(self, color): """ Returns a brush for the color. """ result = self._brushes.get(color) if result is None: qcolor = self._get_color(color) result = QtGui.QBrush(qcolor) self._brushes[color] = result return result def _get_color(self, color): """ Returns a QColor built from a Pygments color string. """ qcolor = QtGui.QColor() qcolor.setRgb(int(color[:2], base=16), int(color[2:4], base=16), int(color[4:6], base=16)) return qcolor
class QPygmentsHighlighter(QSyntaxHighlighter): """ Syntax highlighter that uses Pygments for parsing. """ hilighlightingBlock = Signal(unicode, QSyntaxHighlighter) #--------------------------------------------------------------------------- # 'QSyntaxHighlighter' interface #--------------------------------------------------------------------------- def __init__(self, parent, lexer=None): super(QPygmentsHighlighter, self).__init__(parent) self._document = QtGui.QTextDocument() self._formatter = HtmlFormatter(nowrap=True) self._lexer = lexer if lexer else PythonLexer() self.style = styles.getStyle("Default").pygmentsStyle self.enabled = True def setLexerFromFilename(self, filename): """ Change the lexer based on the filename (actually only the extension is needed) :param filename: Filename or extension """ try: self._lexer = get_lexer_for_filename(filename) except ClassNotFound: self._lexer = PythonLexer() def highlightBlock(self, text): """ Highlight a block of text """ if self.enabled is False: return text = unicode(text) original_text = text prev_data = self.currentBlock().previous().userData() if prev_data is not None: self._lexer._saved_state_stack = prev_data.syntax_stack elif hasattr(self._lexer, '_saved_state_stack'): del self._lexer._saved_state_stack # Lex the text using Pygments index = 0 for token, text in self._lexer.get_tokens(text): length = len(text) self.setFormat(index, length, self._get_format(token)) index += length if hasattr(self._lexer, '_saved_state_stack'): data = PygmentsBlockUserData( syntax_stack=self._lexer._saved_state_stack) self.currentBlock().setUserData(data) # Clean up for the next go-round. del self._lexer._saved_state_stack #Spaces expression = QRegExp('\s+') index = expression.indexIn(original_text, 0) while index >= 0: index = expression.pos(0) length = len(expression.cap(0)) self.setFormat(index, length, self._get_format(Whitespace)) index = expression.indexIn(original_text, index + length) self.hilighlightingBlock.emit(original_text, self) # expression = QRegExp('\s+') # index = expression.indexIn(original_text, 0) # while index >= 0: # index = expression.pos(0) # length = len(expression.cap(0)) # self.setFormat(index, length, self._get_format(Whitespace)) # index = expression.indexIn(original_text, index + length) #--------------------------------------------------------------------------- # 'PygmentsHighlighter' interface #--------------------------------------------------------------------------- def __set_style(self, style): """ Sets the style to the specified Pygments style. """ if (isinstance(style, str) or isinstance(style, unicode)): style = get_style_by_name(style) self._style = style self._clear_caches() def set_style_sheet(self, stylesheet): """ Sets a CSS stylesheet. The classes in the stylesheet should correspond to those generated by: pygmentize -S <style> -f html Note that 'set_style' and 'set_style_sheet' completely override each other, i.e. they cannot be used in conjunction. """ self._document.setDefaultStyleSheet(stylesheet) self._style = None self._clear_caches() def __get_style(self): return self._style #: gets/sets the **pygments** style. style = property(__get_style, __set_style) #--------------------------------------------------------------------------- # Protected interface #--------------------------------------------------------------------------- def _clear_caches(self): """ Clear caches for brushes and formats. """ self._brushes = {} self._formats = {} def _get_format(self, token): """ Returns a QTextCharFormat for token or None. """ if token in self._formats: return self._formats[token] if self._style is None: result = self._get_format_from_document(token, self._document) else: result = self._get_format_from_style(token, self._style) self._formats[token] = result return result def _get_format_from_document(self, token, document): """ Returns a QTextCharFormat for token by """ code, html = next(self._formatter._format_lines([(token, 'dummy')])) self._document.setHtml(html) return QtGui.QTextCursor(self._document).charFormat() def _get_format_from_style(self, token, style): """ Returns a QTextCharFormat for token by reading a Pygments style. """ result = QtGui.QTextCharFormat() for key, value in list(style.style_for_token(token).items()): if value: if key == 'color': result.setForeground(self._get_brush(value)) elif key == 'bgcolor': result.setBackground(self._get_brush(value)) elif key == 'bold': result.setFontWeight(QtGui.QFont.Bold) elif key == 'italic': result.setFontItalic(True) elif key == 'underline': result.setUnderlineStyle( QtGui.QTextCharFormat.SingleUnderline) elif key == 'sans': result.setFontStyleHint(QtGui.QFont.SansSerif) elif key == 'roman': result.setFontStyleHint(QtGui.QFont.Times) elif key == 'mono': result.setFontStyleHint(QtGui.QFont.TypeWriter) return result def _get_brush(self, color): """ Returns a brush for the color. """ result = self._brushes.get(color) if result is None: qcolor = self._get_color(color) result = QtGui.QBrush(qcolor) self._brushes[color] = result return result def _get_color(self, color): """ Returns a QColor built from a Pygments color string. """ color = unicode(color).replace("#", "") qcolor = QtGui.QColor() qcolor.setRgb(int(color[:2], base=16), int(color[2:4], base=16), int(color[4:6], base=16)) return qcolor
class PygmentsSyntaxHighlighter(SyntaxHighlighter): """ This mode enable syntax highlighting using the pygments library Here the properties added by the mode to :attr:`pyqode.core.QCodeEdit.style`: ====================== ====================== ======= ====================== ===================== Key Section Type Default value Description ====================== ====================== ======= ====================== ===================== pygmentsStyle General QColor Computed. Background color for matching symbols ====================== ====================== ======= ====================== ===================== .. warning:: There are some issues with multi-line comments, they are not properly highlighted until a full re-highlight is triggered. The text is automatically re-highlighted on save. """ #: Mode description DESCRIPTION = "Apply syntax highlighting to the editor using pygments" #: Associates a fold detector to a specific pygments lexer. try: LEXERS_FOLD_DETECTORS = { # indent based PythonLexer: IndentBasedFoldDetector(), CythonLexer: IndentBasedFoldDetector(), BashLexer: IndentBasedFoldDetector(), BatchLexer: IndentBasedFoldDetector(), XmlLexer: IndentBasedFoldDetector(), HtmlLexer: IndentBasedFoldDetector(), JsonLexer: IndentBasedFoldDetector(), BooLexer: IndentBasedFoldDetector(), MakefileLexer: IndentBasedFoldDetector(), CMakeLexer: IndentBasedFoldDetector(), RstLexer: IndentBasedFoldDetector(), # c family CLexer: CharBasedFoldDetector(), CppLexer: CharBasedFoldDetector(), CSharpLexer: CharBasedFoldDetector(), ActionScriptLexer: CharBasedFoldDetector(), CoffeeScriptLexer: CharBasedFoldDetector(), CssLexer: CharBasedFoldDetector(), JavascriptLexer: CharBasedFoldDetector(), JavaLexer: CharBasedFoldDetector(), QmlLexer: CharBasedFoldDetector(), PhpLexer: CharBasedFoldDetector(), AdaLexer: CharBasedFoldDetector(), CudaLexer: CharBasedFoldDetector(), DLexer: CharBasedFoldDetector(), GLShaderLexer: CharBasedFoldDetector(), GoLexer: CharBasedFoldDetector(), ObjectiveCLexer: CharBasedFoldDetector(), ObjectiveCppLexer: CharBasedFoldDetector(), ValaLexer: CharBasedFoldDetector(), } except NameError: logger.warning("PygmentsSyntaxHighlighter: Failed to setup fold " "detectors associations. Please upgrade your pygments " "installation.") LEXERS_FOLD_DETECTORS = {} @property def pygmentsStyle(self): return self.editor.style.value("pygmentsStyle") @pygmentsStyle.setter def pygmentsStyle(self, value): return self.editor.style.setValue("pygmentsStyle", value) def __init__(self, document, lexer=None): super(PygmentsSyntaxHighlighter, self).__init__(document) self._document = QtGui.QTextDocument() self._formatter = HtmlFormatter(nowrap=True) self._lexer = lexer if lexer else PythonLexer() self.__previousFilename = "" self.style = "default" def _onInstall(self, editor): """ :type editor: pyqode.code.QCodeEdit """ SyntaxHighlighter._onInstall(self, editor) self.triggers = ["*", '**', '"', "'", "/"] self._clear_caches() self.prev_txt = "" style = editor.style.addProperty("pygmentsStyle", "default") self.style = style self.editor.style.setValue( "background", QtGui.QColor(self.style.background_color)) c = self.style.style_for_token(Text)['color'] if c is None: c = '000000' self.editor.style.setValue( "foreground", QtGui.QColor("#{0}".format(c))) def _onStateChanged(self, state): self.enabled = state if state is True: self.editor.textSaved.connect(self.rehighlight) else: self.editor.textSaved.disconnect(self.rehighlight) self.rehighlight() def __updateLexer(self): self.setLexerFromFilename(self.editor.fileName) if hasattr(self.editor, "foldingPanel"): if type(self._lexer) in self.LEXERS_FOLD_DETECTORS: self.setFoldDetector( self.LEXERS_FOLD_DETECTORS[type(self._lexer)]) self.editor.foldingPanel.enabled = True else: self.editor.foldingPanel.enabled = False def __onTextSaved(self): self.rehighlight() def _onStyleChanged(self, section, key): """ Updates the pygments style """ if key == "pygmentsStyle" or not key: self.style = self.editor.style.value( "pygmentsStyle") self.rehighlight() self.editor.style.setValue( "background", QtGui.QColor(self.style.background_color)) c = self.style.style_for_token(Text)['color'] if c is None: c = '000000' self.editor.style.setValue( "foreground", QtGui.QColor("#{0}".format(c))) def setLexerFromFilename(self, filename): """ Change the lexer based on the filename (actually only the extension is needed) :param filename: Filename or extension """ try: if filename.endswith("~"): filename = filename[0:len(filename) - 1] self._lexer = get_lexer_for_filename(filename) except ClassNotFound: logger.warning("Failed to find lexer from filename %s" % filename) self._lexer = None def setLexerFromMimeType(self, mime, **options): try: self._lexer = get_lexer_for_mimetype(mime, **options) except ClassNotFound: logger.warning("Failed to find lexer from mime type %s" % mime) self._lexer = None def doHighlightBlock(self, text): fn = self.editor.fileName if fn != self.__previousFilename: self.__previousFilename = fn self.__updateLexer() if self._lexer is None: return #Spaces expression = QRegExp('\s+') index = expression.indexIn(text, 0) while index >= 0: index = expression.pos(0) length = len(expression.cap(0)) self.setFormat(index, length, self._get_format(Whitespace)) index = expression.indexIn(text, index + length) if self.enabled is False: return prev_data = self.currentBlock().previous().userData() if hasattr(prev_data, "syntax_stack"): self._lexer._saved_state_stack = prev_data.syntax_stack elif hasattr(self._lexer, '_saved_state_stack'): del self._lexer._saved_state_stack # Lex the text using Pygments index = 0 usd = self.currentBlock().userData() usd.cc_disabled_zones[:] = [] for token, text in self._lexer.get_tokens(text): length = len(text) if "comment" in str(token).lower(): # to the end usd.cc_disabled_zones.append((index, pow(2, 32))) elif "string" in str(token).lower(): usd.cc_disabled_zones.append((index, index + length)) self.setFormat(index, length, self._get_format(token)) index += length if hasattr(self._lexer, '_saved_state_stack'): data = self.currentBlock().userData() setattr(data, "syntax_stack", self._lexer._saved_state_stack) self.currentBlock().setUserData(data) # Clean up for the next go-round. del self._lexer._saved_state_stack def __set_style(self, style): """ Sets the style to the specified Pygments style. """ if (isinstance(style, str) or isinstance(style, unicode)): style = get_style_by_name(style) self._style = style self._clear_caches() def __get_style(self): return self._style #: gets/sets the **pygments** style. style = property(__get_style, __set_style) def _clear_caches(self): """ Clear caches for brushes and formats. """ self._brushes = {} self._formats = {} def _get_format(self, token): """ Returns a QTextCharFormat for token or None. """ if token in self._formats: return self._formats[token] if self._style is None: result = self._get_format_from_document(token, self._document) else: result = self._get_format_from_style(token, self._style) self._formats[token] = result return result def _get_format_from_document(self, token, document): """ Returns a QTextCharFormat for token by """ code, html = next(self._formatter._format_lines([(token, 'dummy')])) self._document.setHtml(html) return QtGui.QTextCursor(self._document).charFormat() def _get_format_from_style(self, token, style): """ Returns a QTextCharFormat for token by reading a Pygments style. """ result = QtGui.QTextCharFormat() for key, value in list(style.style_for_token(token).items()): if value: if key == 'color': result.setForeground(self._get_brush(value)) elif key == 'bgcolor': result.setBackground(self._get_brush(value)) elif key == 'bold': result.setFontWeight(QtGui.QFont.Bold) elif key == 'italic': result.setFontItalic(True) elif key == 'underline': result.setUnderlineStyle( QtGui.QTextCharFormat.SingleUnderline) elif key == 'sans': result.setFontStyleHint(QtGui.QFont.SansSerif) elif key == 'roman': result.setFontStyleHint(QtGui.QFont.Times) elif key == 'mono': result.setFontStyleHint(QtGui.QFont.TypeWriter) return result def _get_brush(self, color): """ Returns a brush for the color. """ result = self._brushes.get(color) if result is None: qcolor = self._get_color(color) result = QtGui.QBrush(qcolor) self._brushes[color] = result return result @staticmethod def _get_color(color): """ Returns a QColor built from a Pygments color string. """ color = str(color).replace("#", "") qcolor = QtGui.QColor() qcolor.setRgb(int(color[:2], base=16), int(color[2:4], base=16), int(color[4:6], base=16)) return qcolor
class PygmentsHighlighter(gui.SyntaxHighlighter): """Syntax highlighter that uses Pygments for parsing.""" # --------------------------------------------------------------------------- # "QSyntaxHighlighter" interface # --------------------------------------------------------------------------- def __init__(self, parent: QtGui.QTextDocument, lexer: str, style: str = "default"): super().__init__(parent) self._document = self.document() self._formatter = HtmlFormatter(nowrap=True) self.set_style(style) if lexer == "regex": self._lexer = load_lexer_from_file(str(paths.RE_LEXER_PATH)) else: self._lexer = get_lexer_by_name(lexer) def __repr__(self): return f"{type(self).__name__}(lexer={self._lexer.aliases[0]!r})" def highlightBlock(self, string): """Highlight a block of text.""" prev_data = self.currentBlock().previous().userData() if prev_data is not None: self._lexer._saved_state_stack = prev_data.syntax_stack elif hasattr(self._lexer, "_saved_state_stack"): del self._lexer._saved_state_stack # Lex the text using Pygments index = 0 for token, text in self._lexer.get_tokens(string): length = qstring_length(text) self.setFormat(index, length, self._get_format(token)) index += length if hasattr(self._lexer, "_saved_state_stack"): data = gui.TextBlockUserData( syntax_stack=self._lexer._saved_state_stack) self.currentBlock().setUserData(data) # Clean up for the next go-round. del self._lexer._saved_state_stack # --------------------------------------------------------------------------- # "PygmentsHighlighter" interface # --------------------------------------------------------------------------- def set_style(self, style: None | str | Style): if style is None: style = get_style_by_name("default") elif isinstance(style, str): style = get_style_by_name(style) self._style = style self._clear_caches() def set_style_sheet(self, stylesheet: str): """Sets a CSS stylesheet. The classes in the stylesheet should correspond to those generated by: pygmentize -S <style> -f html Note that "set_style" and "set_style_sheet" completely override each other, i.e. they cannot be used in conjunction. """ self._document.setDefaultStyleSheet(stylesheet) self._style = None self._clear_caches() # --------------------------------------------------------------------------- # Protected interface # --------------------------------------------------------------------------- def _clear_caches(self): """Clear caches for brushes and formats.""" self._get_brush.cache_clear() self._get_format.cache_clear() @functools.lru_cache(maxsize=None) def _get_format(self, token: str) -> QtGui.QTextCharFormat: """Returns a QTextCharFormat for token or None.""" if self._style is None: return self._get_format_from_document(token, self._document) else: return self._get_format_from_style(token, self._style) def _get_format_from_document( self, token: str, document: QtGui.QTextDocument) -> QtGui.QTextCharFormat: """Return a QTextCharFormat for token from document.""" code, html = next(self._formatter._format_lines([(token, "dummy")])) self._document.setHtml(html) return gui.TextCursor(self._document).charFormat() def _get_format_from_style(self, token: str, style: Style) -> gui.TextCharFormat: """Return a QTextCharFormat for token by reading a Pygments style.""" result = gui.TextCharFormat() try: token_style = style.style_for_token(token) except KeyError: return result for key, value in token_style.items(): if value: if key == "color": result.set_foreground_color(self._get_brush(value)) elif key == "bgcolor": result.set_background_color(self._get_brush(value)) elif key == "bold": result.set_font_weight("bold") elif key == "italic": result.setFontItalic(True) elif key == "underline": result.set_underline_style("single") elif key == "sans": result.set_font_style_hint("sans_serif") elif key == "roman": result.set_font_style_hint("serif") elif key == "mono": result.set_font_style_hint("typewriter") return result @functools.lru_cache(maxsize=None) def _get_brush(self, color: str) -> gui.Brush: """Return a brush for the color.""" qcolor = gui.Color(f"#{color[:6]}") return gui.Brush(qcolor)