语法高亮示例

语法高亮示例展示了如何执行简单的语法高亮。

语法高亮应用显示了具有自定义语法高亮的 C++ 文件。

该示例包含两个类

  • Highlighter 类定义并应用高亮规则。
  • MainWindow 组件是应用程序的主窗口。

我们首先回顾 Highlighter 类,看看如何自定义 QSyntaxHighlighter 类来满足您的喜好,然后我们将浏览 MainWindow 类的相关部分,看看您如何可以在应用程序中使用您自定义的高亮类。

Highlighter 类定义

class Highlighter : public QSyntaxHighlighter
{
    Q_OBJECT

public:
    Highlighter(QTextDocument *parent = nullptr);

protected:
    void highlightBlock(const QString &text) override;

private:
    struct HighlightingRule
    {
        QRegularExpression pattern;
        QTextCharFormat format;
    };
    QList<HighlightingRule> highlightingRules;

    QRegularExpression commentStartExpression;
    QRegularExpression commentEndExpression;

    QTextCharFormat keywordFormat;
    QTextCharFormat classFormat;
    QTextCharFormat singleLineCommentFormat;
    QTextCharFormat multiLineCommentFormat;
    QTextCharFormat quotationFormat;
    QTextCharFormat functionFormat;
};

要提供自己的语法高亮,您必须创建 QSyntaxHighlighter 的子类,重新实现 highlightBlock() 函数,并定义您自己的高亮规则。

我们选择使用私有结构来存储我们的高亮规则:一个规则由一个 QRegularExpression 模式和一个 QTextCharFormat 实例组成。然后使用 QList 存储各种规则。

QTextCharFormat 类提供了对 QTextDocument 中的字符进行格式化的信息,指定文本的视觉属性,以及关于它在超文本文档中的角色的信息。在本例中,我们将只使用 QTextCharFormat::setFontWeight() 和 QTextCharFormat::setForeground() 函数来定义字体粗细和颜色。

Highlighter 类实现

当创建 QSyntaxHighlighter 类的子类时,您必须将父参数传递给基类构造函数。父类是指将要应用语法高亮的文本文档。在本例中,我们还选择在构造函数中定义我们的高亮规则

Highlighter::Highlighter(QTextDocument *parent)
    : QSyntaxHighlighter(parent)
{
    HighlightingRule rule;

    keywordFormat.setForeground(Qt::darkBlue);
    keywordFormat.setFontWeight(QFont::Bold);
    const QString keywordPatterns[] = {
        QStringLiteral("\\bchar\\b"), QStringLiteral("\\bclass\\b"), QStringLiteral("\\bconst\\b"),
        QStringLiteral("\\bdouble\\b"), QStringLiteral("\\benum\\b"), QStringLiteral("\\bexplicit\\b"),
        QStringLiteral("\\bfriend\\b"), QStringLiteral("\\binline\\b"), QStringLiteral("\\bint\\b"),
        QStringLiteral("\\blong\\b"), QStringLiteral("\\bnamespace\\b"), QStringLiteral("\\boperator\\b"),
        QStringLiteral("\\bprivate\\b"), QStringLiteral("\\bprotected\\b"), QStringLiteral("\\bpublic\\b"),
        QStringLiteral("\\bshort\\b"), QStringLiteral("\\bsignals\\b"), QStringLiteral("\\bsigned\\b"),
        QStringLiteral("\\bslots\\b"), QStringLiteral("\\bstatic\\b"), QStringLiteral("\\bstruct\\b"),
        QStringLiteral("\\btemplate\\b"), QStringLiteral("\\btypedef\\b"), QStringLiteral("\\btypename\\b"),
        QStringLiteral("\\bunion\\b"), QStringLiteral("\\bunsigned\\b"), QStringLiteral("\\bvirtual\\b"),
        QStringLiteral("\\bvoid\\b"), QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bbool\\b")
    };
    for (const QString &pattern : keywordPatterns) {
        rule.pattern = QRegularExpression(pattern);
        rule.format = keywordFormat;
        highlightingRules.append(rule);
    }

首先,我们定义一个关键字规则,该规则识别最常见的 C++ 关键字。我们为 keywordFormat 指定加粗、深蓝色字体。对于每个关键字,我们将关键字和指定的格式分配给 HighlightingRule 对象,并将对象追加到规则列表中。

    classFormat.setFontWeight(QFont::Bold);
    classFormat.setForeground(Qt::darkMagenta);
    rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b"));
    rule.format = classFormat;
    highlightingRules.append(rule);

    quotationFormat.setForeground(Qt::darkGreen);
    rule.pattern = QRegularExpression(QStringLiteral("\".*\""));
    rule.format = quotationFormat;
    highlightingRules.append(rule);

    functionFormat.setFontItalic(true);
    functionFormat.setForeground(Qt::blue);
    rule.pattern = QRegularExpression(QStringLiteral("\\b[A-Za-z0-9_]+(?=\\()"));
    rule.format = functionFormat;
    highlightingRules.append(rule);

然后,我们创建了一个格式,我们将将其应用于 Qt 类名。类名将以深洋红色和加粗样式呈现。我们指定一个字符串模式,它实际上是一个正则表达式,用于捕获所有 Qt 类名。然后我们将正则表达式和指定的格式分配给 HighlightingRule 对象,并将对象追加到规则列表中。

我们还定义了使用相同方法为引文和函数定义突出显示规则:模式具有正则表达式的形式,并以关联的格式存储在突出显示规则对象中。

    singleLineCommentFormat.setForeground(Qt::red);
    rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*"));
    rule.format = singleLineCommentFormat;
    highlightingRules.append(rule);

    multiLineCommentFormat.setForeground(Qt::red);

    commentStartExpression = QRegularExpression(QStringLiteral("/\\*"));
    commentEndExpression = QRegularExpression(QStringLiteral("\\*/"));
}

C++语言有两种注释变体:单行注释(《code translate="no">//》)和多行注释(《code translate="no">/*...*//)。可以通过类似之前的规则轻松地为单行注释定义突出显示规则。但多行注释需要特别注意,因为QSyntaxHighlighter类的构造。

创建QSyntaxHighlighter对象后,其highlightBlock()函数将在富文本引擎需要时自动调用,以突出显示给定的文本块。当注释跨越多个文本块时就会出现问题。在审查Highlighter::highlightBlock()函数的实现时,我们将更详细地了解如何解决这个问题。在此阶段,我们仅指定多行注释的颜色。

void Highlighter::highlightBlock(const QString &text)
{
    for (const HighlightingRule &rule : std::as_const(highlightingRules)) {
        QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
        while (matchIterator.hasNext()) {
            QRegularExpressionMatch match = matchIterator.next();
            setFormat(match.capturedStart(), match.capturedLength(), rule.format);
        }
    }

当富文本引擎需要时,highlightBlock()函数会自动调用,即当有文本块发生变化时。

我们首先应用存储在highlightingRules列表中的语法突出显示规则。对于每个规则(即每个突出显示规则对象),我们使用QString::indexOf()函数在给定的文本块中查找模式。当找到模式的第一个出现时,我们使用QRegularExpressionMatch::capturedLength()函数来确定将被格式化的字符串。如果找不到匹配项,QRegularExpressionMatch::capturedLength()返回0。

要执行实际的格式化,QSyntaxHighlighter类提供了一个setFormat()函数。此函数作用于传递给highlightBlock()函数的文本块。指定的格式应用于从给定起始位置到给定长度的文本。在显示时间,设置在给定格式中的格式属性与直接存储在文档中的格式信息合并。请注意,通过此函数设置的格式不会修改文档本身。

重复此过程,直到当前文本块中找到模式的最后一个出现。

    setCurrentBlockState(0);

为了处理可以跨越多个文本块的构造(如C++的多行注释),有必要知道前一个文本块的结束状态(例如,"在注释中")。你可以在你的highlightBlock()实现中,使用QSyntaxHighlighter::previousBlockState()函数查询前一个文本块的结束状态。在解析块之后,你可以使用QSyntaxHighlighter::setCurrentBlockState()保存最后的状态。

previousBlockState()函数返回一个整数值。如果未设置状态,则返回值为-1。你可以使用setCurrentBlockState()函数指定任何其他值以标识任何给定的状态。一旦设置了状态,QTextBlock将保持该值,直到再次将其设置,或者直到相应的文本段落被删除。

在这个例子中,我们选择使用0表示"不在注释中"状态,并使用1表示"在注释中"状态。在应用存储的语法突出显示规则时,我们将当前块状态初始化为0。

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

如果上一个区块状态是“在注释中”(previousBlockState() == 1),我们从文本块的开始处开始搜索结束表达式。如果previousBlockState()返回0,我们从开始表达式的首次出现位置开始搜索。

    while (startIndex >= 0) {
        QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
        int endIndex = match.capturedStart();
        int commentLength = 0;
        if (endIndex == -1) {
            setCurrentBlockState(1);
            commentLength = text.length() - startIndex;
        } else {
            commentLength = endIndex - startIndex
                            + match.capturedLength();
        }
        setFormat(startIndex, commentLength, multiLineCommentFormat);
        startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
    }
}

找到结束表达式后,我们计算注释的长度并应用多行注释格式。然后我们继续搜索下一个开始表达式的出现,并重复此过程。如果在当前文本块中找不到结束表达式,我们将当前区块状态设置为1,即“在注释中”。

这完成了Highlighter类的实现;它现在可以使用了。

MainWindow类定义

使用QSyntaxHighlighter子类很简单;只需为您的应用程序提供一个该类的实例,并传入要应用高亮功能的文档。

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

public slots:
    void about();
    void newFile();
    void openFile(const QString &path = QString());

private:
    void setupEditor();
    void setupFileMenu();
    void setupHelpMenu();

    QTextEdit *editor;
    Highlighter *highlighter;
};

在这个例子中,我们声明了一个指向Highlighter实例的指针,我们稍后将在这个私有的setupEditor()函数中对其进行初始化。

MainWindow类实现

主窗口的构造函数很简单。我们首先设置菜单,然后初始化编辑器并使其成为应用程序的中心小部件。最后,我们设置主窗口的标题。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setupFileMenu();
    setupHelpMenu();
    setupEditor();

    setCentralWidget(editor);
    setWindowTitle(tr("Syntax Highlighter"));
}

我们在私有的setupEditor()便利函数中初始化并安装了Highlighter对象

void MainWindow::setupEditor()
{
    QFont font;
    font.setFamily("Courier");
    font.setFixedPitch(true);
    font.setPointSize(10);

    editor = new QTextEdit;
    editor->setFont(font);

    highlighter = new Highlighter(editor->document());

    QFile file("mainwindow.h");
    if (file.open(QFile::ReadOnly | QFile::Text))
        editor->setPlainText(file.readAll());
}

首先,我们创建我们希望在编辑器中使用的字体,然后我们创建编辑器本身,它是QTextEdit类的实例。在我们用MainWindow类定义的文件初始化编辑器之前,我们创建一个传递编辑器文档作为参数的Highlighter实例。这是高亮功能将应用于的文档。然后我们就完成了。

QSyntaxHighlighter对象一次只能安装在一个文档上,但您可以使用QSyntaxHighlighter::setDocument()函数轻松地将高亮器重新安装到另一个文档上。《a href="qsyntaxhighlighter.html" translate="no">QSyntaxHighlighter类还提供了一个document()函数,它返回当前设置的文档。

示例项目 @ code.qt.io

© 2024 Qt公司。在此包含的文档贡献的版权属于各自的所有者。在此提供的文档是根据自由软件基金会发布的GNU自由文档许可证第1.3版条款许可的。