界面非常简单没啥可说的:
#include "mainwindow.h"
#include <QtWidgets>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupFileMenu();
setupEditor();
setCentralWidget(editor);
setWindowTitle(tr("语法高亮器"));
}
void MainWindow::newFile()
{
editor->clear();
}
void MainWindow::openFile(const QString &path)
{
QString fileName = path;
if (fileName.isNull())
fileName = QFileDialog::getOpenFileName(this, tr("打开文件"), "", "C++文件 (*.cpp *.h *.hpp)");
if (!fileName.isEmpty())
{
QFile file(fileName);
if (file.open(QFile::ReadOnly | QFile::Text))
editor->setPlainText(file.readAll());
}
}
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());
}
void MainWindow::setupFileMenu()
{
QMenu *fileMenu = new QMenu(tr("&File"), this);
menuBar()->addMenu(fileMenu);
fileMenu->addAction(tr("&New"), QKeySequence::New,this, &MainWindow::newFile);
fileMenu->addAction(tr("&Open..."), QKeySequence::Open,this, [this](){ openFile(); });
fileMenu->addAction(tr("E&xit"), QKeySequence::Quit,qApp, &QApplication::quit);
}
主要来看看自定义语法高亮器的用法。
要实现自定义高亮规则需要继承 QSyntaxHighlighter 类并重新实现 highlightBlock()。
定义变量:
常用的变量:
格式设置为暗蓝色加粗:
在正则表达式里面,“\b”表示单词边界的断言,匹配的不一定是空格,只要是一个确定不是单词内容的字符即可匹配。如:
这里的-、=、+、:等符号不被认为是单词的内容即匹配上了。
又例如匹配成功:
匹配不成功:
类名:
暗洋红色加粗格式,匹配以Q为开头后面至少跟一个大小写字符的内容。
单行注释:
红色,匹配以“//”开头后面跟0个或多个不包含换行符的字符。
双引号引用的文本:
暗绿色,匹配前后为“"”,中间包含0个或多个任意字符的内容。
函数名:
蓝色斜体,匹配以至少一个字母或数字或下划线开头且后面跟“(”的内容。这个规则是不严谨的,要是根据这个规则,下面这个错误的函数名也能匹配上:
不过问题不大,知道它的意思就行了。
多行注释:
“/\\*”中的“\\”表示转义字符“\”,实际上想表示的内容是:/\*。
“\”在正则表达式有表示将下一个字符标记为原意字符的意思(),“\*”表示:字符“*”。
即“/\\*”表示:字符串“/*”。同理“\\*/”表示:字符串“*/”。
实现的 highlightBlock() 函数:
void Highlighter::highlightBlock(const QString &text)
{
for (const HighlightingRule &rule : qAsConst(highlightingRules))
{
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
startIndex = text.indexOf(commentStartExpression);
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);
}
}
此函数在文档内容变化时会自动调用:
每次变化时传入的参数就是变化的文本块的文本:
高亮规则列表里的正则表达式挨个匹配,匹配上的就设置格式,这个没啥可说的,主要看一下多行注释的匹配。
Qt富文本的文本块(QTextBlock)提供了一个“状态”属性,即一个整数,默认为-1,可以通过设置文本块的状态来对文本块进行标记区分。
要处理可以跨越多个文本块的结构,需要知道上一个文本块的结束状态,使用previousBlockState() 函数可查询上一个文本块的结束状态。解析块后,可以使用 setCurrentBlockState() 设置当前文本块的状态。
在本例中,规定如果是要设置多行注释的文本块则状态值设为1。
如果上一个文本块中的文本不是多行注释的内容,则先查找当前文本是否有匹配 “/*” 的内容,indexOf() 如果查找到则返回匹配文本的索引位置,未找到则返回-1。
如果在当前文本块中找到 “/*”,则继续查找是否有 “*/”,如果没找到就认为 “*/” 在下面,当前文本属于多行注释中的内容即设置当前文本块状态为1。
这个类完整代码:
#include "highlighter.h"
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);
}
classFormat.setFontWeight(QFont::Bold);
classFormat.setForeground(Qt::darkMagenta);
rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b"));
rule.format = classFormat;
highlightingRules.append(rule);
singleLineCommentFormat.setForeground(Qt::red);
rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*"));
rule.format = singleLineCommentFormat;
highlightingRules.append(rule);
multiLineCommentFormat.setForeground(Qt::red);
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);
commentStartExpression = QRegularExpression(QStringLiteral("/\\*"));
commentEndExpression = QRegularExpression(QStringLiteral("\\*/"));
}
void Highlighter::highlightBlock(const QString &text)
{
for (const HighlightingRule &rule : qAsConst(highlightingRules))
{
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
startIndex = text.indexOf(commentStartExpression);
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);
}
}