Qt小例子学习87 - 语法高亮
HighlightingRule.h
#ifndef SYNTAXHIGHLIGHTER_H
#define SYNTAXHIGHLIGHTER_H
#include <QRegularExpression>
#include <QSyntaxHighlighter>
class QQuickTextDocument;
class HighlightingRule
{
public:
HighlightingRule(const QString &patternStr, int n,
const QTextCharFormat &matchingFormat);
QString originalRuleStr;
QRegularExpression pattern;
int nth;
QTextCharFormat format;
};
class PythonSyntaxHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
PythonSyntaxHighlighter(QTextDocument *parent);
const QTextCharFormat getTextCharFormat(const QString &colorName,
const QString &style = QString());
void initializeRules();
bool matchMultiline(const QString &text, const QRegularExpression &delimiter,
const int inState, const QTextCharFormat &style);
protected:
void highlightBlock(const QString &text);
private:
QStringList keywords;
QStringList operators;
QStringList braces;
QHash<QString, QTextCharFormat> basicStyles;
QList<HighlightingRule> rules;
QRegularExpression triSingleQuote;
QRegularExpression triDoubleQuote;
};
class SyntaxHighlighterHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QQuickTextDocument *quickdocument READ quickdocument WRITE
setQuickdocument NOTIFY quickdocumentChanged)
public:
SyntaxHighlighterHelper(QObject *parent = nullptr)
: QObject(parent), m_quickdocument(nullptr) {}
QQuickTextDocument *quickdocument() const;
void setQuickdocument(QQuickTextDocument *quickdocument);
signals:
void quickdocumentChanged();
private:
QQuickTextDocument *m_quickdocument;
};
#endif // SYNTAXHIGHLIGHTER_H
HighlightingRule.cpp
#include "syntaxhighlighter.h"
#include <QQuickTextDocument>
HighlightingRule::HighlightingRule(const QString &patternStr, int n,
const QTextCharFormat &matchingFormat)
{
originalRuleStr = patternStr;
pattern = QRegularExpression(patternStr);
nth = n;
format = matchingFormat;
}
PythonSyntaxHighlighter::PythonSyntaxHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
keywords = QStringList() << "and"
<< "assert"
<< "break"
<< "class"
<< "continue"
<< "def"
<< "del"
<< "elif"
<< "else"
<< "except"
<< "exec"
<< "finally"
<< "for"
<< "from"
<< "global"
<< "if"
<< "import"
<< "in"
<< "is"
<< "lambda"
<< "not"
<< "or"
<< "pass"
<< "print"
<< "raise"
<< "return"
<< "try"
<< "while"
<< "yield"
<< "None"
<< "True"
<< "False";
operators = QStringList() << "=" <<
// Comparison
"=="
<< "!="
<< "<"
<< "<="
<< ">"
<< ">=" <<
// Arithmetic
"\\+"
<< "-"
<< "\\*"
<< "/"
<< "//"
<< "%"
<< "\\*\\*" <<
// In-place
"\\+="
<< "-="
<< "\\*="
<< "/="
<< "%=" <<
// Bitwise
"\\^"
<< "\\|"
<< "&"
<< "~"
<< ">>"
<< "<<";
braces = QStringList() << "{"
<< "}"
<< "\\("
<< "\\)"
<< "\\["
<< "]";
basicStyles.insert("keyword", getTextCharFormat("blue"));
basicStyles.insert("operator", getTextCharFormat("red"));
basicStyles.insert("brace", getTextCharFormat("darkGray"));
basicStyles.insert("defclass", getTextCharFormat("black", "bold"));
basicStyles.insert("brace", getTextCharFormat("darkGray"));
basicStyles.insert("string", getTextCharFormat("magenta"));
basicStyles.insert("string2", getTextCharFormat("darkMagenta"));
basicStyles.insert("comment", getTextCharFormat("darkGreen", "italic"));
basicStyles.insert("self", getTextCharFormat("black", "italic"));
basicStyles.insert("numbers", getTextCharFormat("brown"));
triSingleQuote.setPattern("'''");
triDoubleQuote.setPattern("\"\"\"");
initializeRules();
}
void PythonSyntaxHighlighter::initializeRules()
{
for (const QString &currKeyword : keywords)
{
rules.append(HighlightingRule(QString("\\b%1\\b").arg(currKeyword), 0,
basicStyles.value("keyword")));
}
for (const QString &currOperator : operators)
{
rules.append(HighlightingRule(QString("%1").arg(currOperator), 0,
basicStyles.value("operator")));
}
for (const QString &currBrace : braces)
{
rules.append(HighlightingRule(QString("%1").arg(currBrace), 0,
basicStyles.value("brace")));
}
// 'self'
rules.append(HighlightingRule("\\bself\\b", 0, basicStyles.value("self")));
// Double-quoted string, possibly containing escape sequences
// FF: originally in python : r'"[^"\\]*(\\.[^"\\]*)*"'
rules.append(HighlightingRule("\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"", 0,
basicStyles.value("string")));
// Single-quoted string, possibly containing escape sequences
// FF: originally in python : r"'[^'\\]*(\\.[^'\\]*)*'"
rules.append(HighlightingRule("'[^'\\\\]*(\\\\.[^'\\\\]*)*'", 0,
basicStyles.value("string")));
// 'def' followed by an identifier
// FF: originally: r'\bdef\b\s*(\w+)'
rules.append(HighlightingRule("\\bdef\\b\\s*(\\w+)", 1,
basicStyles.value("defclass")));
// 'class' followed by an identifier
// FF: originally: r'\bclass\b\s*(\w+)'
rules.append(HighlightingRule("\\bclass\\b\\s*(\\w+)", 1,
basicStyles.value("defclass")));
// From '#' until a newline
// FF: originally: r'#[^\\n]*'
rules.append(HighlightingRule("#[^\\n]*", 0, basicStyles.value("comment")));
// Numeric literals
rules.append(HighlightingRule(
"\\b[+-]?[0-9]+[lL]?\\b", 0,
basicStyles.value("numbers"))); // r'\b[+-]?[0-9]+[lL]?\b'
rules.append(HighlightingRule(
"\\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\\b", 0,
basicStyles.value("numbers"))); // r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b'
rules.append(HighlightingRule(
"\\b[+-]?[0-9]+(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b", 0,
basicStyles.value(
"numbers"))); // r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b'
}
void PythonSyntaxHighlighter::highlightBlock(const QString &text)
{
for (const HighlightingRule &rule : rules)
{
QRegularExpressionMatchIterator matchIterator =
rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
setCurrentBlockState(0);
bool isInMultilne =
matchMultiline(text, triSingleQuote, 1, basicStyles.value("string2"));
if (!isInMultilne)
isInMultilne =
matchMultiline(text, triDoubleQuote, 2, basicStyles.value("string2"));
}
bool PythonSyntaxHighlighter::matchMultiline(
const QString &text, const QRegularExpression &delimiter, const int inState,
const QTextCharFormat &style)
{
QRegularExpressionMatch match;
int startIndex = 0;
if (previousBlockState() != 1)
startIndex = text.indexOf(delimiter);
while (startIndex >= 0)
{
QRegularExpressionMatch match = delimiter.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, style);
startIndex = text.indexOf(delimiter, startIndex + commentLength);
}
return currentBlockState() == inState;
}
const QTextCharFormat
PythonSyntaxHighlighter::getTextCharFormat(const QString &colorName,
const QString &style)
{
QTextCharFormat charFormat;
QColor color(colorName);
charFormat.setForeground(color);
if (style.contains("bold", Qt::CaseInsensitive))
charFormat.setFontWeight(QFont::Bold);
if (style.contains("italic", Qt::CaseInsensitive))
charFormat.setFontItalic(true);
return charFormat;
}
QQuickTextDocument *SyntaxHighlighterHelper::quickdocument() const
{
return m_quickdocument;
}
void SyntaxHighlighterHelper::setQuickdocument(
QQuickTextDocument *quickdocument)
{
m_quickdocument = quickdocument;
if (m_quickdocument)
{
new PythonSyntaxHighlighter(m_quickdocument->textDocument());
}
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.12
import Foo 1.0
Window {
id: mainWindow
visible: true
width: 640
height: 480
title: qsTr("Hello World")
SyntaxHighlighterHelper{
quickdocument: ta.textDocument
}
TextArea{
id:ta
anchors.fill: parent
selectByMouse: true
selectByKeyboard: true
font.pointSize: 12
textMargin: 16
font.family:"courier new"
persistentSelection: true
textFormat: Text.StyledText
tabStopDistance: 4*fontMetrics.advanceWidth(" ")
FontMetrics {
id: fontMetrics
font.family: ta.font
}
}
}
main.cpp
#include "syntaxhighlighter.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qmlRegisterType<SyntaxHighlighterHelper>("Foo", 1, 0,
"SyntaxHighlighterHelper");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}