Syntax highlighting with PyQt

Thu 02 July 2009

A few months ago I decided to add syntax highlighting capabilities to a piece of software that I have been working on. Since it is a PyQt based application, the obvious choice for implementing syntax highlighting was to use Qt’s QSyntaxHighlighter. Unfortunately, there weren’t many examples around that implemented syntax highlighting in Python, so I decided to post my own.

The Python file used in this example is available here. To implement syntax highlighting, we need to subclass QSyntaxHighlighter, reimplement the highlightBlock function, and specify several highlighting rules. Generally, a rule consists of a QRegExp pattern and a QTextCharFormat instance. For this example, the syntax rules are based on the R statistical programming language. The various rules can be stored using a Python list.

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MyHighlighter( QSyntaxHighlighter ):
    def __init__( self, parent, theme ):
        QSyntaxHighlighter.__init__( self, parent )
        self.parent = parent
        self.highlightingRules = []

        keyword = QTextCharFormat()
        keyword.setForeground( Qt.darkBlue )
        keyword.setFontWeight( QFont.Bold )
        keywords = QStringList( [ "break", "else", "for", "if", "in",
                                  "next", "repeat", "return", "switch",
                                  "try", "while" ] )
        for word in keywords:
            pattern = QRegExp("\\b" + word + "\\b")
            rule = HighlightingRule( pattern, keyword )
            self.highlightingRules.append( rule )

MyHighlighter is the subclassed QSyntaxHighlighter class, and will contain our reimplemented highlightBlock function. The above example is for the keyword rule, which recognizes the most common R keywords. We give keyword a bold, dark blue font. For each keyword, we assign the keyword and the specified format to a HighlightingRule object (see the attached Python file) and append the object to our list of rules. We can specify further syntax rules, including reservedClasses, assignmentOperators, and numbers:

reservedClasses = QTextCharFormat()
reservedClasses.setForeground( Qt.darkRed )
reservedClasses.setFontWeight( QFont.Bold )
keywords = QStringList( [ "array", "character", "complex",
                          "data.frame", "double", "factor",
                          "function", "integer", "list",
                          "logical", "matrix", "numeric",
                          "vector" ] )
for word in keywords:
    pattern = QRegExp("\\b" + word + "\\b")
    rule = HighlightingRule( pattern, reservedClasses )
    self.highlightingRules.append( rule )
assignmentOperator = QTextCharFormat()
pattern = QRegExp( "(<){1,2}-" )
assignmentOperator.setForeground( )
assignmentOperator.setFontWeight( QFont.Bold )
rule = HighlightingRule( pattern, assignmentOperator )
self.highlightingRules.append( rule )
number = QTextCharFormat()
pattern = QRegExp( "[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?" )
pattern.setMinimal( True )
number.setForeground( )
rule = HighlightingRule( pattern, number )
self.highlightingRules.append( rule )

After a QSyntaxHighlighter object is created, its highlightBlock() function will be called automatically whenever it is necessary by the rich text engine, highlighting the given text block. To perform the actual formatting, the QSyntaxHighlighter class provides the setFormat function. This function operates on the text block that is passed as argument to the highlightBlock function. The specified format is applied to the text from the given start position for the given length. The formatting properties set in the given format are merged at display time with the formatting information stored directly in the document.

def highlightBlock( self, text ):
    for rule in self.highlightingRules:
        expression = QRegExp( rule.pattern )
        index = expression.indexIn( text )
        while index >= 0:
            length = expression.matchedLength()
            self.setFormat( index, length, rule.format )
            index = text.indexOf( expression, index + length )
    self.setCurrentBlockState( 0 )

This process is repeated until the last occurrence of the pattern in the current text block is found. For rules that apply over multiple blocks or lines, further logic is needed. For an example, see the QSynatxHighlighter documentation. In order to apply the syntax highlighter to a QTextEdit, we simply create an instance of our QSyntaxHighlighter subclass, and pass it the QTextEdit or QTextDocument that we want the syntax highlighting to be applied to, as the following test application demonstrates:

class TestApp( QMainWindow ):
    def __init__(self):
        editor = QTextEdit()
        highlighter = MyHighlighter( editor )
        self.setCentralWidget( editor )
        self.setWindowTitle( "Syntax Highlighter Example" )

Once implemented, the above example produces output like this:


Helpful Tip

Python Coding FOSS UX Helpful Tip


recent visitors