|  | Home · Examples

# Syntax Highlighter Example

Code:

- SyntaxHighlighter.java

The Syntax Highlighter example shows how to perform simple syntax highlighting by subclassing the QSyntaxHighlighter class.
The Syntax Highlighter application displays Java files with custom syntax highlighting.

The example consists of several classes: The `SyntaxHighlighter` class which extends QMainWindow and provides the main application window, the `Highlighter` class which extends QSyntaxHighlighter, providing the actual syntax highlighting, and the `HighlightingRule` class defining the rules used by the syntax highlighter.

We will first review the `Highlighter` and `HighlightingRule` classes to see how you can customize the QSyntaxHighlighter class to fit your preferences, then we will take a look at the relevant parts of the `SyntaxHighlighter` class to see how you can use your custom highlighter class in an application.

## Highlighter Class Implementation

To provide your own syntax highlighting, you must subclass QSyntaxHighlighter, reimplement the highlightBlock() method, and define your own highlighting rules.

        private class Highlighter extends QSyntaxHighlighter {

            QRegExp commentStartExpression;
            QRegExp commentEndExpression;

            QTextCharFormat keywordFormat = new QTextCharFormat();
            QTextCharFormat classFormat = new QTextCharFormat();
            QTextCharFormat commentFormat = new QTextCharFormat();
            QTextCharFormat quotationFormat = new QTextCharFormat();
            QTextCharFormat functionFormat = new QTextCharFormat();

            public class HighlightingRule {
                public QRegExp pattern;
                public QTextCharFormat format;

                public HighlightingRule(QRegExp pattern, QTextCharFormat format) {
                    this.pattern = pattern;
                    this.format = format;
                }

            }

We have chosen to store our highlighting rules using the private `HighlightingRule` class: A rule consists of a QRegExp pattern and a QTextCharFormat instance.

The QTextCharFormat class provides formatting information for characters in a QTextDocument specifying the visual properties of the text, as well as information about its role in a hypertext document. In this example, we will only define the font weight and color using the QTextCharFormat.setFontWeight() and QTextCharFormat.setForeground() methods.

            Vector<HighlightingRule> highlightingRules = new Vector<HighlightingRule>();

We will use a vector to store our highlighting rules.

            public Highlighter(QTextDocument parent) {

                super(parent);

                HighlightingRule rule;
                QBrush brush;
                QRegExp pattern;

When subclassing the QSyntaxHighlighter class you must pass the constructor's `parent` parameter to the super class's constructor. The `parent` is the text document upon which the syntax highligning will be applied. In this example, we have also chosen to define our highlighting rules in the constructor:

                brush = new QBrush(QColor.darkBlue,Qt.BrushStyle.SolidPattern);
                keywordFormat.setForeground(brush);
                keywordFormat.setFontWeight(QFont.Weight.Bold.value());

                String[] keywords = { "abstract", "continue", "for", "new",
                                      "switch", "assert", "default", "goto",
                                      "package", "synchronized", "boolean",
                                      "do", "if", "private", "this", "break",
                                      "double", "implements", "protected",
                                      "throw", "byte", "else", "import",
                                      "public", "throws", "case", "enum",
                                      "instanceof", "return", "transient",
                                      "catch", "extends", "int", "short",
                                      "try", "char", "final", "interface",
                                      "static", "void", "class", "finally",
                                      "long", "strictfp", "volatile", "const",
                                      "float", "native", "super", "while" };

                for (String keyword : keywords) {
                    pattern = new QRegExp("\\b" + keyword + "\\b");
                    rule = new HighlightingRule(pattern, keywordFormat);
                    highlightingRules.add(rule);
                }

First we define a keyword rule which recognizes the most common Java keywords. We give the `keywordFormat` a bold, dark blue font. For each keyword, we assign the keyword and the specified format to a `HighlightingRule` object and append the object to our list of rules.

                brush = new QBrush(QColor.darkMagenta);
                pattern = new QRegExp("\\bQ[A-Za-z]+\\b");
                classFormat.setForeground(brush);
                classFormat.setFontWeight(QFont.Weight.Bold.value());
                rule = new HighlightingRule(pattern, classFormat);
                highlightingRules.add(rule);

                brush = new QBrush(QColor.gray, Qt.BrushStyle.SolidPattern);
                pattern = new QRegExp("//[^\n]*");
                commentFormat.setForeground(brush);
                rule = new HighlightingRule(pattern, commentFormat);
                highlightingRules.add(rule);

                brush = new QBrush(QColor.blue, Qt.BrushStyle.SolidPattern);
                pattern = new QRegExp("\".*\"");
                pattern.setMinimal(true);
                quotationFormat.setForeground(brush);
                rule = new HighlightingRule(pattern, quotationFormat);
                highlightingRules.add(rule);

Then we create a format that we will apply to Qt class names. The class names will be rendered with a dark magenta color and a bold style. We specify a string pattern that is actually a regular expression capturing all Qt class names. Then we assign the regular expression and the specified format to a `HighlightingRule` object and add the object to our list of rules.

We also define highlighting rules for quotations and methods using the same approach: The patterns have the form of regular expressions and are stored in `HighlightingRule` objects with the associated format.

                brush = new QBrush(QColor.gray, Qt.BrushStyle.SolidPattern);
                pattern = new QRegExp("//[^\n]*");
                commentFormat.setForeground(brush);
                rule = new HighlightingRule(pattern, commentFormat);
                highlightingRules.add(rule);

                commentStartExpression = new QRegExp("/\\*");
                commentEndExpression = new QRegExp("\\");
            }

The Java language has two variations of comments: The single line comment (//) and the multiline comment (/*...* /). The single line comment can easily be defined through a highlighting rule similar to the previous ones. But the multiline comment needs special care due to the design of the QSyntaxHighlighter class.

After a QSyntaxHighlighter object is created, its highlightBlock() method will be called automatically whenever it is necessary by the rich text engine, highlighting the given text block. The problem appears when a comment spans several text blocks.

            public void highlightBlock(String text) {

                for (HighlightingRule rule : highlightingRules) {
                    QRegExp expression = rule.pattern;
                    int index = expression.indexIn(text);
                    while (index >= 0) {
                        int length = expression.matchedLength();
                        setFormat(index, length, rule.format);
                        index = expression.indexIn(text, index + length);
                    }
                }

The `highlightBlock()` method is called automatically whenever there are text blocks that have changed.

First we apply the syntax highlighting rules that we stored in the `highlightingRules` vector. For each rule (i.e. for each `HighlightingRule` object) we search for the pattern in the given textblock using the QRegExp.indexIn() method. When the first occurrence of the pattern is found, we use the QRegExp.matchedLength() method to determine the string that will be formatted. QRegExp.matchedLength() returns the length of the last matched string, or -1 if there was no match.

To perform the actual formatting the QSyntaxHighlighter class provides the setFormat() method. This method operates on the text block that is passed as argument to the `highlightBlock()` method. 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. Note that the document itself remains unmodified by the format set through this method.

This process is repeated until the last occurrence of the pattern in the current text block is found.

                setCurrentBlockState(0);

To deal with constructs that can span several text blocks (like the Java multiline comment), it is necessary to know the end state of the previous text block (e.g. "in comment"). Inside your `highlightBlock()` implementation you can query the end state of the previous text block using the QSyntaxHighlighter.previousBlockState() method. After parsing the block you can save the last state using QSyntaxHighlighter's setCurrentBlockState() method.

The previousBlockState() method return an int value. If no state is set, the returned value is -1. You can designate any other value to identify any given state using the setCurrentBlockState() method. Once the state is set, the QTextBlock keeps that value until it is set again or until the corresponding paragraph of text is deleted.

In this example we have chosen to use 0 to represent the "not in comment" state, and 1 for the "in comment" state. When the stored syntax highlighting rules are applied, we initialize the current block state to 0.

                int startIndex = 0;
                if (previousBlockState() != 1)
                    startIndex = commentStartExpression.indexIn(text);

If the previous block state was "in comment" (`previousBlockState() == 1`), we start the search for an end expression at the beginning of the text block. If the previousBlockState() method returns 0, we start the search at the location of the first occurrence of a start expression.

                while (startIndex >= 0) {

                    int endIndex = commentEndExpression.indexIn(text, startIndex);
                    int commentLength;
                    if (endIndex == -1) {
                        setCurrentBlockState(1);
                        commentLength = text.length() - startIndex;
                    } else {

                        commentLength = endIndex - startIndex + commentEndExpression.matchedLength();
                    }

                    setFormat(startIndex, commentLength, commentFormat);
                    startIndex = commentStartExpression.indexIn(text, startIndex + commentLength);
                }

            }

When an end expression is found, we calculate the length of the comment and apply the multiline comment format. Then we search for the next occurrence of the start expression and repeat the process. If no end expression can be found in the current text block we set the current block state to 1, i.e. "in comment".

This completes the `Highlighter` class implementation; it is now ready for use.

## SyntaxHighlighter Class Implementation

The constructor of the main application window is straight forward. We first set up the menus, then we initialize the editor and make it the central widget of the application. Finally, we set the main window's title and icon:

    public class SyntaxHighlighter extends QMainWindow {

        private QTextEdit editor;

        public SyntaxHighlighter() {
            setupFileMenu();
            setupHelpMenu();
            setupEditor();

            setCentralWidget(editor);
            resize(640, 480);
            setWindowTitle(tr("Syntax Highlighter"));
            setWindowIcon(new QIcon(
                          "classpath:com/trolltech/images/qt-logo.png"));
        }

Using a QSyntaxHighlighter subclass is simple; just provide your application with an instance of the class and pass it the document upon which you want the highlighting to be applied.

We initialize and install the `Highlighter` object in the private `setupEditor()` convenience method:

        private void setupEditor() {
            QFont font = new QFont();
            font.setFamily("Courier");
            font.setFixedPitch(true);
            font.setPointSize(10);

            editor = new QTextEdit();
            editor.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap);
            editor.setFont(font);

            new Highlighter(editor.document());

            QFile file = new QFile(
                   "classpath:com/trolltech/examples/SyntaxHighlighter.java");

            if (file.open(new QFile.OpenMode(QFile.OpenModeFlag.ReadOnly,
                                             QFile.OpenModeFlag.Text)))
                editor.setPlainText(file.readAll().toString());
        }

First we create the font we want to use in the editor, then we create the editor itself which is an instance of the QTextEdit class. Before we initialize the editor with the `SyntaxHighlighter.java` file, we create a `Highlighter` instance passing the editor's document as argument. This is the document that the highlighting will be applied to. Then we are done.

A QSyntaxHighlighter object can only be installed on one document at the time, but you can easily reinstall the highlighter on another document using the QSyntaxHighlighter.setDocument() method. The QSyntaxHighlighter class also provides the document() method which returns the currently set document.

        public static void main(String args[]) {
            QApplication.initialize(args);

            SyntaxHighlighter syntaxHighlighter = new SyntaxHighlighter();
            syntaxHighlighter.show();

            QApplication.exec();
        }

    }

Finally, we provide a `main()` method to create and show the main application window when the example is run.