Пример #1
0
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
Пример #2
0
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
Пример #3
0
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
Пример #5
0
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)