效果图,左侧显示行号,右侧用TextArea显示文本内容,并且语法高亮。
2025年5月8号更新
1、多行文本注释
多行文本注释跟普通的高亮规则代码不太一样,代码需要修改,这里以JavaScript举例。
先制定多行文本注释规则:
QVector<QPair<QRegularExpression, QTextCharFormat>> getJSMultiLineRules()
{
QTextCharFormat multiLineCommentFormat;
multiLineCommentFormat.setForeground(Qt::darkGreen);
multiLineCommentFormat.setFontItalic(true);
QVector<QPair<QRegularExpression, QTextCharFormat>> m_rules;
m_rules << qMakePair(QRegularExpression("/\\*"), multiLineCommentFormat); // start
m_rules << qMakePair(QRegularExpression("\\*/"), multiLineCommentFormat); // end
return m_rules;
}
然后我们设置规则的时候,需要先设置完普通的规则,再设置多行文本规则:
void SyntaxHighlighter::highlightBlock(const QString &text)
{
for (const auto &rule : m_rules) {
QRegularExpressionMatchIterator it = rule.first.globalMatch(text);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.second);
}
}
// 再设置多行规则
setCurrentBlockState(0);
if (m_language == JavaScript) {
QVector<QPair<QRegularExpression, QTextCharFormat>> rules = getJSMultiLineRules();
for (int i = 0; i < rules.size(); i+=2) { // +=2是因为多行的开头和结尾是一个配对,有两条规则
QPair<QRegularExpression, QTextCharFormat> startRules = rules[i];
QPair<QRegularExpression, QTextCharFormat> endRules = rules[i+1];
int startIndex = 0;
if (previousBlockState() != 1)
startIndex = text.indexOf(startRules.first);
while (startIndex >= 0) {
QRegularExpressionMatch match = endRules.first.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, startRules.second);
startIndex = text.indexOf(startRules.first, startIndex + commentLength);
}
}
}
}
最后实现结果:
2、单行文本注释
单行文本注释,需要放在所有普通注释的规则之后,以免被覆盖。
否则就会出现这种情况,举个例子:
以下是正文
需要实现的功能:
1、左侧显示行号
2、右侧TextArea
3、可显示语法高亮
1、左侧显示行号
这里我用了一个ListView,让它跟TextView的行数对应起来,并且可以一起滚动。
简单的做法是,将ListView和TextView都放在一个ScrollView中,这样滚动的时候就可以让TextView和ListView一起滚动了。
我之前就是这么做的,但是后面发现TextView中有过长的内容时,横向滚动会把ListView滚走,这不是我想要的……
所以,我把ListView放在ScrollView的外面,看代码:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Rectangle {
id: breakListRec
x: 5
width: 50
height: textArea.height
anchors.verticalCenter: textArea.verticalCenter
color: "#F1F1F1"
clip: true
ListView {
id: breakListView
anchors.fill: parent
model: textArea.lineCount
clip: true
contentY: textArea.contentY
interactive: false
delegate: Item {
width: breakListView.width
height: index === 0 ? (textArea.lineHeight + textArea.topPadding/2) : textArea.lineHeight
Rectangle {
width: 1
height: parent.height
color: "#999999"
anchors.right: parent.right
}
Text {
text: qsTr(String(index+1))
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 8
font.pixelSize: 20
color: "#888888"
}
}
}
}
这里的几个重点:
1、ListView的model为textArea的lineCount;
2、ListView的contentY绑定到TextArea的contentY属性上,当然TextArea本身是没有这个属性的,这是我自己自定义算出来的;
3、ListView中的delegate的height,如果是第一行的话,需要注意的是TextArea本身有一个topPadding,所以要把这个也带上,然后TextArea本身也是没有lineHeight属性的,这个也是我自定义算出来的;
2、右侧TextArea
再看看右侧的TextArea怎么实现的,首先它肯定是放在一个ScrollView中的,其次我们需要实现行号需要的那几个属性值,看代码:
Item {
anchors.right: parent.right
anchors.rightMargin: 5
anchors.left: breakListRec.right
anchors.top: header.bottom
anchors.topMargin: 5
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
property int lineCount: textArea.lineCount
property int lineHeight: textArea.cursorRectangle.height
property real contentY: textAreaScroll.contentHeight * textAreaScroll.ScrollBar.vertical.position
property int topPadding: textArea.topPadding
ScrollView {
id: textAreaScroll
anchors.fill: parent
clip: true
background: Rectangle { color: "#F1F1F1" }
TextArea {
id: textArea
background: Rectangle { color: "#F1F1F1" }
font.pixelSize: 20
selectByMouse: true
selectionColor: "#87cefa"
leftPadding: 0
}
}
}
textArea.cursorRectangle.height可以获取到TextArea中一行的真实高度;
contentY需要用到滚动条的position来进行计算;
3、可显示语法高亮
这是本文的重点,这里采用了cpp中的QSyntaxHighlighter类,能更方便地定制高亮规则。
我这里简单定制了JSON, CPP, Python, JavaScript四种规则,可以相互切换;
首先我们先定义一个类SyntaxHighlighter,来继承QSyntaxHighlighter;
class SyntaxHighlighter : public QSyntaxHighlighter {}
其次,我们需要在类SyntaxHighlighter中重新实现函数highlightBlock;highlightBlock函数就是能让TextArea应用高亮的函数,在更改完高亮风格后,都必须要重新调用这个函数,以让TextArea刷新高亮风格;
void highlightBlock(const QString &text) override;
然后我们还需要将属性document和language暴露出来,给到qml使用;
Q_PROPERTY(QQuickTextDocument* document READ document WRITE setDocument NOTIFY documentChanged)
Q_PROPERTY(Language language READ language WRITE setLanguage NOTIFY languageChanged)
public:
enum Language { JSON, CPP, Python, JavaScript };
// 设置语法格式
Language language() const;
void setLanguage(Language lang);
// 设置文本内容
QQuickTextDocument* document() const { return m_quickDocument; }
void setDocument(QQuickTextDocument* doc);
signals:
void documentChanged();
void languageChanged();
另外,我们还需要学习两个类,QRegularExpression和QTextCharFormat。
QRegularExpression是用来定制语法识别规则的,比如这样可以识别到单行注释:
QRegularExpression("//[^\n]*")
QTextCharFormat则是用来制定高亮风格的,比如这样可以制定高亮为加粗、蓝色:
QTextCharFormat keywordFormat;
keywordFormat.setForeground(Qt::blue);
keywordFormat.setFontWeight(QFont::Bold);
最后,我们还需要在main中注册这个类,这样qml才能使用:
qmlRegisterType<SyntaxHighlighter>("CustomHighlighter", 1, 0, "SyntaxHighlighter");
我们看看qml怎么使用这个类:
TextArea {
id: textArea
background: Rectangle { color: "#F1F1F1" }
font.pixelSize: 20
selectByMouse: true
selectionColor: "#87cefa"
leftPadding: 0
SyntaxHighlighter {
id: highlighter
document: textArea.textDocument
language: SyntaxHighlighter.CPP
onLanguageChanged: {
var data = textArea.text
textArea.text = ""
textArea.text = data
}
}
}
☆☆ 好了,现在来看完整代码
先制定四种语法规则:
CPPRules.h
#ifndef CPPRULES_H
#define CPPRULES_H
#include <QObject>
#include <QTextCharFormat>
#include <QRegularExpression>
QVector<QPair<QRegularExpression, QTextCharFormat>> getCPPRules()
{
QVector<QPair<QRegularExpression, QTextCharFormat> > m_rules;
// 1. 关键字(蓝色加粗)
QTextCharFormat keywordFormat;
keywordFormat.setForeground(Qt::blue);
keywordFormat.setFontWeight(QFont::Bold);
QStringList keywords = {
"char", "class", "const",
"double", "enum", "explicit",
"friend", "inline", "int",
"long", "namespace", "operator",
"private", "protected", "public",
"short", "signals", "signed",
"slots", "static", "struct",
"template", "typedef", "typename",
"union", "unsigned", "virtual",
"void", "volatile", "bool"
};
for (const QString &kw : keywords) {
m_rules << qMakePair(QRegularExpression("\\b" + kw + "\\b"), keywordFormat);
}
// 2.类名
QTextCharFormat classFormat;
classFormat.setFontWeight(QFont::Bold);
classFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("\\bQ[A-Za-z]+\\b"), classFormat);
// 3. 单行注释(绿色)
QTextCharFormat singleLineCommentFormat;
singleLineCommentFormat.setForeground(Qt::darkGreen);
m_rules << qMakePair(QRegularExpression("//[^\n]*"), singleLineCommentFormat);
// 4. 多行注释(绿色斜体)
QTextCharFormat multiLineCommentFormat;
multiLineCommentFormat.setForeground(Qt::darkGreen);
multiLineCommentFormat.setFontItalic(true);
m_rules << qMakePair(QRegularExpression("/\\*.*?\\*/"), multiLineCommentFormat);
// 5. 字符串(橙色)
QTextCharFormat stringFormat;
stringFormat.setForeground(QColor(255, 165, 0)); // 橙色
m_rules << qMakePair(QRegularExpression("\".*\""), stringFormat);
// 6. 数字(紫色)
QTextCharFormat numberFormat;
numberFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("\\b\\d+\\b"), numberFormat);
// 7. 预处理指令(灰色)
QTextCharFormat preprocessorFormat;
preprocessorFormat.setForeground(Qt::gray);
m_rules << qMakePair(QRegularExpression("#.*"), preprocessorFormat);
// 8.函数名
QTextCharFormat functionFormat;
functionFormat.setForeground(Qt::blue);
m_rules << qMakePair(QRegularExpression("(\\w+)::"), functionFormat);
// 9.被引用,如A::Test中的Test
QTextCharFormat functionTwoFormat;
functionTwoFormat.setForeground(Qt::darkBlue);
m_rules << qMakePair(QRegularExpression("\\b[A-Za-z0-9_]+(?=\\()"), functionTwoFormat);
return m_rules;
}
#endif // CPPRULES_H
JavaScriptRules.h
#ifndef JAVASCRIPTRULES_H
#define JAVASCRIPTRULES_H
#include <QObject>
#include <QTextCharFormat>
#include <QRegularExpression>
QVector<QPair<QRegularExpression, QTextCharFormat>> getJavaScriptRules() {
QVector<QPair<QRegularExpression, QTextCharFormat> > m_rules;
// 1. 关键字(蓝色加粗)
QTextCharFormat keywordFormat;
keywordFormat.setForeground(Qt::blue);
keywordFormat.setFontWeight(QFont::Bold);
QStringList keywords = {
"function", "if", "else", "for", "while", "do", "switch", "case", "break",
"return", "var", "let", "const", "new", "this", "true", "false", "null",
"undefined", "try", "catch", "finally", "throw", "class", "extends", "import",
"export", "async", "await", "yield"
};
for (const QString &kw : keywords) {
m_rules << qMakePair(QRegularExpression("\\b" + kw + "\\b"), keywordFormat);
}
// 2. 内置对象和方法(深蓝色)
QTextCharFormat builtinFormat;
builtinFormat.setForeground(QColor(0, 0, 139)); // 深蓝色
QStringList builtins = {
"console", "Object", "Array", "String", "Number", "Math", "JSON", "Promise",
"setTimeout", "fetch", "document", "window", "require"
};
for (const QString &bn : builtins) {
m_rules << qMakePair(QRegularExpression("\\b" + bn + "\\b"), builtinFormat);
}
// 3. 单行注释(绿色)
QTextCharFormat singleLineCommentFormat;
singleLineCommentFormat.setForeground(Qt::darkGreen);
m_rules << qMakePair(QRegularExpression("//[^\n]*"), singleLineCommentFormat);
// 4. 多行注释(绿色斜体)
QTextCharFormat multiLineCommentFormat;
multiLineCommentFormat.setForeground(Qt::darkGreen);
multiLineCommentFormat.setFontItalic(true);
m_rules << qMakePair(QRegularExpression("/\\*.*?\\*/"), multiLineCommentFormat);
// 5. 字符串(橙色)
QTextCharFormat stringFormat;
stringFormat.setForeground(QColor(255, 165, 0)); // 橙色
// 匹配单引号、双引号、模板字符串
m_rules << qMakePair(QRegularExpression("\".*?\""), stringFormat);
m_rules << qMakePair(QRegularExpression("'.*?'"), stringFormat);
m_rules << qMakePair(QRegularExpression("`.*?`"), stringFormat);
// 6. 正则表达式(紫色)
QTextCharFormat regexFormat;
regexFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("/.+?/[gimuy]*"), regexFormat);
// 7. 数字(紫色)
QTextCharFormat numberFormat;
numberFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("\\b\\d+\\.?\\d*\\b"), numberFormat);
// 8. 函数定义(深红色)
QTextCharFormat functionDefFormat;
functionDefFormat.setForeground(QColor(139, 0, 0)); // 深红色
m_rules << qMakePair(QRegularExpression("\\bfunction\\s+(\\w+)"), functionDefFormat);
m_rules << qMakePair(QRegularExpression("\\b(\\w+)\\s*=\\s*function\\b"), functionDefFormat);
// 9. 箭头函数(深青色)
QTextCharFormat arrowFunctionFormat;
arrowFunctionFormat.setForeground(QColor(0, 139, 139)); // 深青色
m_rules << qMakePair(QRegularExpression("\\b(\\w+)\\s*=>"), arrowFunctionFormat);
return m_rules;
}
#endif // JAVASCRIPTRULES_H
JsonRules.h
#ifndef JSONRULES_H
#define JSONRULES_H
#include <QObject>
#include <QTextCharFormat>
#include <QRegularExpression>
QVector<QPair<QRegularExpression, QTextCharFormat>> getJsonRules() {
QVector<QPair<QRegularExpression, QTextCharFormat> > m_rules;
// 1. JSON Key(深蓝色加粗)
QTextCharFormat keyFormat;
keyFormat.setForeground(Qt::darkBlue);
keyFormat.setFontWeight(QFont::Bold);
m_rules << qMakePair(QRegularExpression("\"(\\w+)\"\\s*:"), keyFormat);
// 2. JSON String Value(绿色)
QTextCharFormat stringValueFormat;
stringValueFormat.setForeground(Qt::darkGreen);
m_rules << qMakePair(QRegularExpression("\".*\""), stringValueFormat);
// 3. JSON Number(紫色)
QTextCharFormat numberFormat;
numberFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("\\b\\d+\\b"), numberFormat);
return m_rules;
}
#endif // JSONRULES_H
PythonRules.h
#ifndef PYTHONRULES_H
#define PYTHONRULES_H
#include <QObject>
#include <QTextCharFormat>
#include <QRegularExpression>
QVector<QPair<QRegularExpression, QTextCharFormat>> getPythonRules() {
QVector<QPair<QRegularExpression, QTextCharFormat> > m_rules;
// 1. 关键字(蓝色加粗)
QTextCharFormat keywordFormat;
keywordFormat.setForeground(Qt::blue);
keywordFormat.setFontWeight(QFont::Bold);
QStringList keywords = {
"def", "class", "if", "elif", "else", "for", "while",
"try", "except", "finally", "with", "import", "from",
"as", "return", "yield", "lambda", "nonlocal", "global"
};
for (const QString &kw : keywords) {
m_rules << qMakePair(QRegularExpression("\\b" + kw + "\\b"), keywordFormat);
}
// 2. 内置函数和类型(深蓝色)
QTextCharFormat builtinFormat;
builtinFormat.setForeground(QColor(0, 0, 139)); // 深蓝色
QStringList builtins = {
"print", "len", "range", "list", "dict", "str", "int",
"float", "True", "False", "None", "self"
};
for (const QString &bn : builtins) {
m_rules << qMakePair(QRegularExpression("\\b" + bn + "\\b"), builtinFormat);
}
// 3. 单行注释(绿色)
QTextCharFormat commentFormat;
commentFormat.setForeground(Qt::darkGreen);
m_rules << qMakePair(QRegularExpression("#[^\n]*"), commentFormat);
// 4. 字符串(橙色)
QTextCharFormat stringFormat;
stringFormat.setForeground(QColor(255, 165, 0)); // 橙色
// 匹配单引号、双引号、三引号字符串
m_rules << qMakePair(QRegularExpression("\"\"\".*?\"\"\""), stringFormat);
m_rules << qMakePair(QRegularExpression("'''.*?'''"), stringFormat);
m_rules << qMakePair(QRegularExpression("\".*?\""), stringFormat);
m_rules << qMakePair(QRegularExpression("'.*?'"), stringFormat);
// 5. 装饰器(紫色)
QTextCharFormat decoratorFormat;
decoratorFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("@\\w+"), decoratorFormat);
// 6. 数字(紫色)
QTextCharFormat numberFormat;
numberFormat.setForeground(Qt::darkMagenta);
m_rules << qMakePair(QRegularExpression("\\b\\d+\\.?\\d*\\b"), numberFormat);
// 7. 函数定义(深红色)
QTextCharFormat functionDefFormat;
functionDefFormat.setForeground(QColor(139, 0, 0)); // 深红色
m_rules << qMakePair(QRegularExpression("\\bdef\\s+(\\w+)"), functionDefFormat);
return m_rules;
}
#endif // PYTHONRULES_H
再写一个QMLFunction类,用来给qml读取文件等信息
QMLFunction.h
#ifndef QMLFUNCTION_H
#define QMLFUNCTION_H
#include <QUrl>
#include <QFile>
#include <QObject>
#include <QFileInfo>
class QMLFunction : public QObject
{
Q_OBJECT
public:
explicit QMLFunction(QObject *parent = nullptr);
Q_INVOKABLE QString readFile(QUrl filePath);
Q_INVOKABLE void saveFile(QString data);
Q_INVOKABLE int fileLanguage();
private:
QString currentFilePath;
signals:
};
#endif // QMLFUNCTION_H
QMLFunction.cpp
#include "QMLFunction.h"
#include "SyntaxHighlighter.h"
QMLFunction::QMLFunction(QObject *parent)
: QObject{parent}
{
}
QString QMLFunction::readFile(QUrl filePath)
{
currentFilePath = "";
currentFilePath = filePath.path(QUrl::PrettyDecoded);
#ifdef Q_OS_WIN32
if(currentFilePath.startsWith('/')){
currentFilePath = currentFilePath.remove(0,1);
}
#endif
QString data = "";
QFile file(currentFilePath);
if (file.open(QIODevice::ReadOnly)) {
data = file.readAll();
file.close();
}
return data;
}
void QMLFunction::saveFile(QString data)
{
QFile file(currentFilePath);
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
file.write(data.toUtf8());
file.close();
}
}
int QMLFunction::fileLanguage()
{
QFileInfo info(currentFilePath);
QString suffix = info.suffix();
if (suffix == "CPP") {
return SyntaxHighlighter::CPP;
} else if (suffix == "json") {
return SyntaxHighlighter::JSON;
} else if (suffix == "h") {
return SyntaxHighlighter::CPP;
} else if (suffix == "js") {
return SyntaxHighlighter::JavaScript;
} else if (suffix == "py") {
return SyntaxHighlighter::Python;
} else {
return SyntaxHighlighter::CPP;
}
}
再写高亮的主要类SyntaxHighlighter
SyntaxHighlighter.h
#ifndef SYNTAXHIGHLIGHTER_H
#define SYNTAXHIGHLIGHTER_H
#include <QObject>
#include <QQuickTextDocument>
#include <QSyntaxHighlighter>
#include <QRegularExpression>
class SyntaxHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
Q_PROPERTY(QQuickTextDocument* document READ document WRITE setDocument NOTIFY documentChanged)
Q_PROPERTY(Language language READ language WRITE setLanguage NOTIFY languageChanged)
public:
enum Language { JSON, CPP, Python, JavaScript };
Q_ENUM(Language)
SyntaxHighlighter(QTextDocument *parent = nullptr);
// 设置语法格式
Language language() const;
void setLanguage(Language lang);
// 设置文本内容
QQuickTextDocument* document() const { return m_quickDocument; }
void setDocument(QQuickTextDocument* doc);
protected:
void highlightBlock(const QString &text) override;
private:
QQuickTextDocument* m_quickDocument = nullptr;
Language m_language;
QVector<QPair<QRegularExpression, QTextCharFormat>> m_rules;
signals:
void documentChanged();
void languageChanged();
};
#endif // SYNTAXHIGHLIGHTER_H
SyntaxHighlighter.cpp
#include "SyntaxHighlighter.h"
#include "CPPRules.h"
#include "JavaScriptRules.h"
#include "PythonRules.h"
#include "JsonRules.h"
// SyntaxHighlighter.cpp
SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) {
}
SyntaxHighlighter::Language SyntaxHighlighter::language() const
{
return m_language;
}
void SyntaxHighlighter::setLanguage(Language lang)
{
if (m_language == lang) return ;
m_language = lang;
m_rules.clear();
switch (lang) {
case JSON: {
m_rules = getJsonRules();
break;
}
case CPP: {
m_rules = getCPPRules();
break;
}
case Python: {
m_rules = getPythonRules();
break;
}
case JavaScript:{
m_rules = getJavaScriptRules();
break;
}
}
rehighlight(); // 重新应用高亮
emit languageChanged(); // 触发信号
}
void SyntaxHighlighter::setDocument(QQuickTextDocument *doc)
{
if (doc != m_quickDocument) {
m_quickDocument = doc;
QSyntaxHighlighter::setDocument(doc->textDocument()); // 关键转换
emit documentChanged();
}
}
void SyntaxHighlighter::highlightBlock(const QString &text)
{
for (const auto &rule : m_rules) {
QRegularExpressionMatchIterator it = rule.first.globalMatch(text);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.second);
}
}
}
main.cpp
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "QMLFunction.h"
#include "SyntaxHighlighter.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
qmlRegisterType<SyntaxHighlighter>("CustomHighlighter", 1, 0, "SyntaxHighlighter");
QQmlApplicationEngine engine;
QMLFunction qmlFunction;
engine.rootContext()->setContextProperty("QMLFunc", &qmlFunction);
qmlRegisterType<QMLFunction>("QMLEnum",1,0,"QMLEnum");
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
再看一下qml的文件代码
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: window_
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
anchors.fill: parent
Header{
id: header
}
LineCountList {
id: breakListRec
}
MyTextArea {
id: textArea
}
}
}
Header.qml
import QtQuick 2.15
import QtQuick.Dialogs 1.3
import QtQuick.Controls 2.15
Item {
id: header
width: parent.width
height: 40
Row {
height: parent.height
width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
Rectangle {
id: fileOpen
width: 70
height: 30
color: fileOpenMouse.pressed ? "#dcdcdc" : "transparent"
border.width: fileOpenMouse.pressed ? 1 : 0
border.color: "#bcbcbc"
anchors.verticalCenter: parent.verticalCenter
ToolTip.visible: fileOpenMouse.entered_
ToolTip.text: qsTr("快捷键 Ctrl+O")
Text {
text: qsTr("打开")
anchors.centerIn: parent
font.pixelSize: 16
}
MouseArea {
id: fileOpenMouse
anchors.fill: parent
hoverEnabled: true
property bool entered_: false
onClicked: {
fileDialog.open()
}
onEntered: {
entered_ = true
}
onExited: {
entered_ = false
}
}
}
Rectangle {
id: fileSave
width: 70
height: 30
color: fileSaveMouse.pressed ? "#dcdcdc" : "transparent"
border.width: fileSaveMouse.pressed ? 1 : 0
border.color: "#bcbcbc"
anchors.verticalCenter: parent.verticalCenter
ToolTip.visible: fileSaveMouse.entered_
ToolTip.text: qsTr("快捷键 Ctrl+O")
Text {
text: qsTr("保存")
anchors.centerIn: parent
font.pixelSize: 16
}
MouseArea {
id: fileSaveMouse
anchors.fill: parent
hoverEnabled: true
property bool entered_: false
onClicked: {
QMLFunc.saveFile(textArea.getText())
}
onEntered: {
entered_ = true
}
onExited: {
entered_ = false
}
}
}
Item {
id: languageItem
width: 150
height: 35
Text {
id: languageTitle
text: qsTr("语法选择")
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 16
}
ComboBox {
id: languageSelect
model: ["JSON", "CPP", "Python", "JavaScript"]
onCurrentIndexChanged: {
if (currentIndex !== textArea.getLanguage()) {
textArea.setLanguage(currentIndex)
}
}
}
}
}
Rectangle {
width: parent.width
height: 1
color: "#444444"
anchors.bottom: parent.bottom
}
FileDialog {
id: fileDialog
onAccepted: {
var data = QMLFunc.readFile(fileUrl)
textArea.setText(data)
textArea.setLanguage(QMLFunc.fileLanguage())
languageSelect.currentIndex = QMLFunc.fileLanguage()
}
}
Shortcut {
sequence: "Ctrl+O"
onActivated: {
fileDialog.open()
}
}
Shortcut {
sequence: "Ctrl+S"
onActivated: {
QMLFunc.saveFile(textArea.getText())
}
}
}
LineCountList.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Rectangle {
id: breakListRec
x: 5
width: 50
height: textArea.height
anchors.verticalCenter: textArea.verticalCenter
color: "#F1F1F1"
clip: true
ListView {
id: breakListView
anchors.fill: parent
model: textArea.lineCount
clip: true
contentY: textArea.contentY
interactive: false
delegate: Item {
width: breakListView.width
height: index === 0 ? (textArea.lineHeight + textArea.topPadding/2) : textArea.lineHeight
Rectangle {
width: 1
height: parent.height
color: "#999999"
anchors.right: parent.right
}
Text {
text: qsTr(String(index+1))
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 8
font.pixelSize: 20
color: "#888888"
}
MouseArea {
anchors.fill: parent
onClicked: {
textArea.selectLine(index)
}
}
}
}
}
MyTextArea.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import CustomHighlighter 1.0
Item {
anchors.right: parent.right
anchors.rightMargin: 5
anchors.left: breakListRec.right
anchors.top: header.bottom
anchors.topMargin: 5
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
property int lineCount: textArea.lineCount
property int lineHeight: textArea.cursorRectangle.height
property real contentY: textAreaScroll.contentHeight * textAreaScroll.ScrollBar.vertical.position
property int topPadding: textArea.topPadding
ScrollView {
id: textAreaScroll
anchors.fill: parent
clip: true
background: Rectangle { color: "#F1F1F1" }
TextArea {
id: textArea
background: Rectangle { color: "#F1F1F1" }
font.pixelSize: 20
selectByMouse: true
selectionColor: "#87cefa"
leftPadding: 0
SyntaxHighlighter {
id: highlighter
document: textArea.textDocument
language: SyntaxHighlighter.CPP
onLanguageChanged: {
var data = textArea.text
textArea.text = ""
textArea.text = data
}
}
}
}
function setText(text) {
textArea.text = text
}
function getText() {
return textArea.text
}
// 选中指定行的函数
function selectLine(lineIndex) {
var lines = textArea.text.split("\n");
if (lineIndex < 0 || lineIndex >= lines.length) return;
// 计算行首位置
var startPos = 0;
for (var i = 0; i < lineIndex; i++) {
startPos += lines[i].length + 1; // +1 是换行符
}
// 计算行尾位置
var endPos = startPos + lines[lineIndex].length+1;
// 选中行并更新当前行
textArea.select(startPos, endPos);
forceActiveFocus();
}
function setLanguage(type) {
highlighter.language = type
}
function getLanguage() {
return highlighter.language;
}
}