Syntax Highlighting in QTextEdit

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 21 | Документация


The appropriate use of colors and fonts to highlight the differentelements of programming and markup languages helpsthe brain to grasp document structures. By flaggingsyntax errors, interactive syntax highlighting also helpsreduce the time spent in the "compile, run, test" cycle.Highlighting can even be used to flag spelling mistakes in textdocuments. With Qt, adding syntax highlighting to a QTextEdit is veryeasy, as this article will demonstrate.

Содержание

Qt 3.1 introduced QSyntaxHighlighteras an abstract base class to provide syntax highlighting to a QTextEdit. In Qt 4, the class was moved to theQt3Support library, and a new QSyntaxHighlighter class took its place.The new QSyntaxHighlighter class isbased on Qt 4's new rich text engine and works on arbitrary QTextDocument objects. In this article, wewill concentrate on the new class.

[править] Subclassing QSyntaxHighlighter

Adding syntax highlighting to a QTextEditinvolves subclassing QSyntaxHighlighter,reimplementing the highlightBlock() function, and instantiating the QSyntaxHighlighter subclass withthe QTextEdit's underlying QTextDocument (returned byQTextEdit::document()) as the parent.

QTextDocument will then callhighlightBlock() for every line in the document as necessary.In the highlightBlock() reimplementation, we can callsetFormat() to set the formatting of the different elements in theline. The following code snippet shows a trivial reimplementation ofhighlightBlock(), where we set every non-alphanumeric character tobe green:

void MyHighlighter::highlightBlock(const QString &text)
{
    for (int i = 0; i < text.length(); ++i) {
        if (!text.at(i).isLetterOrNumber())
            setFormat(i, 1, Qt::green);
    }
}

The QSyntaxHighlighter::setFormat() function exists in threeversions, with the following signatures:

void setFormat(int start, int count, const QTextCharFormat &amp;format);
void setFormat(int start, int count, const QColor &amp;color);
void setFormat(int start, int count, const QFont &amp;font);

The start parameter indicates the start index in the textstring; count, the number of characters. The third parameter canbe a QTextCharFormat object, a QColor, or a QFont.

[править] Keeping Track of State Information

In the trivial example above, the highlighting of each line could bedone independently of other lines. For most realistic languages, thisassumption does not hold. Take a C++ syntax highlighter as anexample. If the user opens a C-style comment on line 10 and closes iton line 15, the lines in between should be highlighted differentlythan if they were not part of a comment.

QSyntaxHighlighter makes this possiblethrough its "state" mechanism. When we finish highlighting a line, we canassociate a state with the line (e.g., "Inside C-Style Comment"), which we canretrieve when we start highlighting the following line. The state isstored as an int.

The following code shows how to handle both C-style and C++-stylecomments in a C++ syntax highlighter. The highlighter has twostates: NormalState and InsideCStyleComment. We defineNormalState as equal to -1 because QSyntaxHighlighter's stateat the top of the document is always -1.

void CppHighlighter::highlightBlock(const QString &amp;text)
{
    enum { NormalState = -1, InsideCStyleComment };
 
    int state = previousBlockState();
    int start = 0;
 
    for (int i = 0; i < text.length(); ++i) {
 
        if (state == InsideCStyleComment) {
            if (text.mid(i, 2) == "*/") {
                state = NormalState;
                setFormat(start, i - start + 2, Qt::blue);
            }
        } else {
            if (text.mid(i, 2) == "//") {
                setFormat(i, text.length() - i, Qt::red);
                break;
            } else if (text.mid(i, 2) == "/*") {
                start = i;
                state = InsideCStyleComment;
            }
        }
    }
    if (state == InsideCStyleComment)
        setFormat(start, text.length() - start, Qt::blue);
 
    setCurrentBlockState(state);
}

At the beginning of the function, we retrieve the previous line'sstate using QSyntaxHighlighter::previousBlockState()and store it in the state local variable. Then we iterate over thecharacters in text and update the state as necessary when we meet/* or */.

We also highlight // comments. Since these comments cannot spanmultiple lines, we don't need a separate state for these. At the endof the function, we call setCurrentBlockState() with the newstate so that it's available when highlighting the next line.

center

In addition to keeping track of the state, we also callsetFormat() to show C-style comments in blue and C++-stylecomments in red.

The Syntax Highlighter example provided with Qt 4.2 alsodemonstrates many of the same principles as this example, but relies on regularexpressions to locate different tokens in C++ source code.The state-based approach used by QSyntaxHighlighter allows for a certaindegree of flexibility in the way highlighters are implemented.

[править] Example: A Basic HTML Highlighter

We will now show the full code for a slightly more complexexample &endash; a class to highlight HTML entities (e.g., &mdash;),tags (e.g.,

</tt>), and comments (e.g., <tt></tt>). In addition to reimplementing <tt>highlightBlock()</tt>, as in the previous example,we also provide functions to let the user of the class specify which colors touse for the various HTML constructs.Let's start with the class definition.

class HtmlHighlighter : public QSyntaxHighlighter
{
    Q_OBJECT
 
public:
    enum Construct {
        Entity,
        Tag,
        Comment,
        LastConstruct = Comment
    };
 
    HtmlHighlighter(QTextDocument *document);
 
    void setFormatFor(Construct construct,
                      const QTextCharFormat &amp;format);
    QTextCharFormat formatFor(Construct construct) const
        { return m_formats[construct]; }
 
protected:
    enum State {
        NormalState = -1,
        InComment,
        InTag
    };
 
    void highlightBlock(const QString &amp;text);
 
private:
    QTextCharFormat m_formats[LastConstruct + 1];
};

The <tt>setFormatFor()</tt> and <tt>formatFor()</tt> functions let the useraccess the formatting used for the supported HTML constructs(entities, tags, and comments). The <tt>State</tt> enum specifies thethree states in which our HTML parser can be in after parsing oneline. The <tt>NormalState</tt> is set to -1, the default state in QSyntaxHighlighter.

center

Let's review the implementation, starting with the constructor:

HtmlHighlighter::HtmlHighlighter(QTextDocument *document)
    : QSyntaxHighlighter(document)
{
    QTextCharFormat entityFormat;
    entityFormat.setForeground(QColor(0, 128, 0));
    entityFormat.setFontWeight(QFont::Bold);
    setFormatFor(Entity, entityFormat);
 
    QTextCharFormat tagFormat;
    tagFormat.setForeground(QColor(192, 16, 112));
    tagFormat.setFontWeight(QFont::Bold);
    setFormatFor(Tag, tagFormat);
 
    QTextCharFormat commentFormat;
    commentFormat.setForeground(QColor(128, 10, 74));
    commentFormat.setFontItalic(true);
    setFormatFor(Comment, commentFormat);
}

In the constructor, we set the default formats for HTML entities,tags, and comments. A format is represented by a QTextCharFormatobject. We specify the properties that we want for the highlightedtext &endash; the foreground color, the font width (bold or not), theunderline style (single, "wiggly"), etc. &endash; and these are applied ontop of the existing attributes of the QTextDocument.

void HtmlHighlighter::setFormatFor(Construct construct,
                            const QTextCharFormat &amp;format)
{
    m_formats[construct] = format;
    rehighlight();
}

The <tt>setFormatFor()</tt> function sets the QTextCharFormat for agiven HTML construct. We callQSyntaxHighlighter::rehighlight()to immediately apply the change on the whole document.

The only function left to review is <tt>highlightBlock()</tt>, which isreimplemented from QSyntaxHighlighter.It's a rather big function, so we will study it chuck by chuck.

void HtmlHighlighter::highlightBlock(const QString &amp;text)
{
    int state = previousBlockState();
    int len = text.length();
    int start = 0;
    int pos = 0;

Like in the C++ highlighter example, we start by retrieving theprevious line's state using <tt>previousBlockState()</tt>. Inside theloop, we switch on the current state, interpreting charactersdifferently depending on whether we are inside a tag or a comment.

  while (pos < len) {
        switch (state) {
        case NormalState:
        default:
            while (pos < len) {
                QChar ch = text.at(pos);
                if (ch == '<') {
                    if (text.mid(pos, 4) == "<!--") {
                        state = InComment;
                    } else {
                        state = InTag;
                    }
                    break;
                } else if (ch == '&amp;') {
                    start = pos;
                    while (pos < len
                           &amp;&amp; text.at(pos++) != ';')
                        ;
                    setFormat(start, pos - start,
                              m_formats[Entity]);
                } else {
                    ++pos;
                }
            }
            break;

For the <tt>NormalState</tt>, we advance looking for a left angle (<tt><</tt>)or an ampersand (<tt>&</tt>) character.

If we hit a left angle (<tt><</tt>), we enter the <tt>InTag</tt> or<tt>InComment</tt> state. If we hit an ampersand (<tt>&</tt>), we dealwith the HTML entity on the spot, formatting it as required. (We couldhave used an <tt>InEntity</tt> state instead and handled it in its own<tt>switch</tt> case, but it isn't necessary because entities, unliketags and comments, cannot span multiple lines.)

      case InComment:
            start = pos;
            while (pos < len) {
                if (text.mid(pos, 3) == "-->") {
                    pos += 3;
                    state = NormalState;
                    break;
                } else {
                    ++pos;
                }
            }
            setFormat(start, pos - start,
                      m_formats[Comment]);
            break;

If we are inside a comment, we look for the closing <tt>--></tt> token. Ifwe find it, we enter the <tt>NormalState</tt>. In all cases, we highlightthe comment usingQSyntaxHighlighter::setFormat().

      case InTag:
            QChar quote = QChar::Null;
            start = pos;
            while (pos < len) {
                QChar ch = text.at(pos);
                if (quote.isNull()) {
                    if (ch == '\" || ch == '"') {
                        quote = ch;
                    } else if (ch == '>') {
                        ++pos;
                        state = NormalState;
                        break;
                    }
                } else if (ch == quote) {
                    quote = QChar::Null;
                }
                ++pos;
            }
            setFormat(start, pos - start, m_formats[Tag]);
        }
    }

If we are inside a tag, we look for the <tt>></tt> token, skipping anyquoted attribute value (e.g., <tt><img alt=">>>>"></tt>). Ifwe find a <tt>></tt>, we enter the <tt>NormalState</tt>. In all cases,we highlight the tag.

  setCurrentBlockState(state);
}

At the end of the function, we store the current state so that thenext line can start in the correct state.

To use the highlighter, we simply need to instantiate it witha QTextDocument as the parent. For example:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QTextEdit editor;
    HtmlSyntaxHighlighter highlighter(editor.document());
    editor.show();
    return app.exec();
}

If we wanted to improve the HTML highlighter further, we would usedifferent highlighting for the tag delimiters, names, attributes, andattribute values.

[править] Summary

QSyntaxHighlighter makes it very easyto write interactive syntax highlighters. When the user edits the text, itonly rehighlights the parts of the text that need to be updated. For example,if the user modifies line 3 of a 2,000-line document, QSyntaxHighlighterwill start by rehighlighting line 3, and will continue only if the stateassociated with line 3 has changed as a result, stopping at the first linefor which the old and new states are identical.

center

In Qt 4, QSyntaxHighlighter isn'trestricted to QTextEdit. It can be used withany editor based on QTextDocument, includingthe new QGraphicsTextItem for QGraphicsView, making it possible toprovide highlighted text editing facilities for transformed text.

The new QSyntaxHighlighter is also extremelyflexible when it comes to handling character formats, thanks to the powerful QTextCharFormat class.