语法高亮示例
语法高亮示例展示了如何执行简单的语法高亮。
语法高亮程序会显示带有自定义语法高亮的 C++ 文件。
该示例由两个类组成:
Highlighter
类定义并应用高亮规则。MainWindow
widget 是应用程序的主窗口。
我们将首先查看Highlighter
类,了解如何根据自己的偏好自定义QSyntaxHighlighter 类,然后再查看MainWindow
类的相关部分,了解如何在应用程序中使用自定义高亮显示类。
高亮类定义
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() 函数定义字体的粗细和颜色。
高亮显示类的实现
在子类化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++ 语言有两种不同的注释:单行注释 (//
) 和多行注释 (/*...*
/
) 。单行注释可以很容易地通过与前面类似的高亮显示规则来定义。但由于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
列表中的语法高亮规则。对于每条规则(即每个 HighlightingRule 对象),我们都会使用QString::indexOf() 函数在给定的文本块中搜索模式。当找到第一个出现的模式时,我们使用QRegularExpressionMatch::capturedLength() 函数来确定要格式化的字符串。QRegularExpressionMatch::capturedLength() 返回最后一个匹配字符串的长度,如果没有匹配字符串,则返回 0。
为了执行实际的格式化,QSyntaxHighlighter 类提供了setFormat() 函数。该函数对作为参数传递给highlightBlock()
函数的文本块进行操作。指定的格式将从给定的起始位置开始应用于给定长度的文本。在显示时,指定格式中设置的格式属性将与直接存储在文档中的格式信息合并。请注意,通过此函数设置的格式不会修改文档本身。
这个过程会一直重复,直到找到当前文本块中最后一次出现的模式。
setCurrentBlockState(0);
要处理可能跨越多个文本块的结构(如 C++ 多行注释),必须知道前一个文本块的结束状态(如 "在注释中")。在highlightBlock()
实现中,您可以使用QSyntaxHighlighter::previousBlockState() 函数查询前一个文本块的结束状态。解析文本块后,您可以使用QSyntaxHighlighter::setCurrentBlockState() 保存最后的状态。
previousBlockState() 函数返回一个 int 值。如果没有设置状态,返回值为-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(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() 函数可以很容易地在另一个文档上重新安装高亮显示器。QSyntaxHighlighter 类还提供了document() 函数,用于返回当前设置的文档。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.