效果图,左侧显示行号,右侧用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); // startm_rules << qMakePair(QRegularExpression("\\*/"), multiLineCommentFormat); // endreturn 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.15Rectangle {id: breakListRecx: 5width: 50height: textArea.heightanchors.verticalCenter: textArea.verticalCentercolor: "#F1F1F1"clip: trueListView {id: breakListViewanchors.fill: parentmodel: textArea.lineCountclip: truecontentY: textArea.contentYinteractive: falsedelegate: Item {width: breakListView.widthheight: index === 0 ? (textArea.lineHeight + textArea.topPadding/2) : textArea.lineHeightRectangle {width: 1height: parent.heightcolor: "#999999"anchors.right: parent.right}Text {text: qsTr(String(index+1))anchors.verticalCenter: parent.verticalCenteranchors.right: parent.rightanchors.rightMargin: 8font.pixelSize: 20color: "#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.rightanchors.rightMargin: 5anchors.left: breakListRec.rightanchors.top: header.bottomanchors.topMargin: 5anchors.bottom: parent.bottomanchors.bottomMargin: 5property int lineCount: textArea.lineCountproperty int lineHeight: textArea.cursorRectangle.heightproperty real contentY: textAreaScroll.contentHeight * textAreaScroll.ScrollBar.vertical.positionproperty int topPadding: textArea.topPaddingScrollView {id: textAreaScrollanchors.fill: parentclip: truebackground: Rectangle { color: "#F1F1F1" }TextArea {id: textAreabackground: Rectangle { color: "#F1F1F1" }font.pixelSize: 20selectByMouse: trueselectionColor: "#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: textAreabackground: Rectangle { color: "#F1F1F1" }font.pixelSize: 20selectByMouse: trueselectionColor: "#87cefa"leftPadding: 0SyntaxHighlighter {id: highlighterdocument: textArea.textDocumentlanguage: SyntaxHighlighter.CPPonLanguageChanged: {var data = textArea.texttextArea.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中的TestQTextCharFormat 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_WIN32if(currentFilePath.startsWith('/')){currentFilePath = currentFilePath.remove(0,1);}
#endifQString 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_OBJECTQ_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);
#endifQGuiApplication 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.15Window {id: window_width: 640height: 480visible: truetitle: qsTr("Hello World")Item {anchors.fill: parentHeader{id: header}LineCountList {id: breakListRec}MyTextArea {id: textArea}}
}
Header.qml
import QtQuick 2.15
import QtQuick.Dialogs 1.3
import QtQuick.Controls 2.15Item {id: headerwidth: parent.widthheight: 40Row {height: parent.heightwidth: parent.width - 10anchors.horizontalCenter: parent.horizontalCenterspacing: 5Rectangle {id: fileOpenwidth: 70height: 30color: fileOpenMouse.pressed ? "#dcdcdc" : "transparent"border.width: fileOpenMouse.pressed ? 1 : 0border.color: "#bcbcbc"anchors.verticalCenter: parent.verticalCenterToolTip.visible: fileOpenMouse.entered_ToolTip.text: qsTr("快捷键 Ctrl+O")Text {text: qsTr("打开")anchors.centerIn: parentfont.pixelSize: 16}MouseArea {id: fileOpenMouseanchors.fill: parenthoverEnabled: trueproperty bool entered_: falseonClicked: {fileDialog.open()}onEntered: {entered_ = true}onExited: {entered_ = false}}}Rectangle {id: fileSavewidth: 70height: 30color: fileSaveMouse.pressed ? "#dcdcdc" : "transparent"border.width: fileSaveMouse.pressed ? 1 : 0border.color: "#bcbcbc"anchors.verticalCenter: parent.verticalCenterToolTip.visible: fileSaveMouse.entered_ToolTip.text: qsTr("快捷键 Ctrl+O")Text {text: qsTr("保存")anchors.centerIn: parentfont.pixelSize: 16}MouseArea {id: fileSaveMouseanchors.fill: parenthoverEnabled: trueproperty bool entered_: falseonClicked: {QMLFunc.saveFile(textArea.getText())}onEntered: {entered_ = true}onExited: {entered_ = false}}}Item {id: languageItemwidth: 150height: 35Text {id: languageTitletext: qsTr("语法选择")anchors.verticalCenter: parent.verticalCenterfont.pixelSize: 16}ComboBox {id: languageSelectmodel: ["JSON", "CPP", "Python", "JavaScript"]onCurrentIndexChanged: {if (currentIndex !== textArea.getLanguage()) {textArea.setLanguage(currentIndex)}}}}}Rectangle {width: parent.widthheight: 1color: "#444444"anchors.bottom: parent.bottom}FileDialog {id: fileDialogonAccepted: {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.15Rectangle {id: breakListRecx: 5width: 50height: textArea.heightanchors.verticalCenter: textArea.verticalCentercolor: "#F1F1F1"clip: trueListView {id: breakListViewanchors.fill: parentmodel: textArea.lineCountclip: truecontentY: textArea.contentYinteractive: falsedelegate: Item {width: breakListView.widthheight: index === 0 ? (textArea.lineHeight + textArea.topPadding/2) : textArea.lineHeightRectangle {width: 1height: parent.heightcolor: "#999999"anchors.right: parent.right}Text {text: qsTr(String(index+1))anchors.verticalCenter: parent.verticalCenteranchors.right: parent.rightanchors.rightMargin: 8font.pixelSize: 20color: "#888888"}MouseArea {anchors.fill: parentonClicked: {textArea.selectLine(index)}}}}}
MyTextArea.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import CustomHighlighter 1.0Item {anchors.right: parent.rightanchors.rightMargin: 5anchors.left: breakListRec.rightanchors.top: header.bottomanchors.topMargin: 5anchors.bottom: parent.bottomanchors.bottomMargin: 5property int lineCount: textArea.lineCountproperty int lineHeight: textArea.cursorRectangle.heightproperty real contentY: textAreaScroll.contentHeight * textAreaScroll.ScrollBar.vertical.positionproperty int topPadding: textArea.topPaddingScrollView {id: textAreaScrollanchors.fill: parentclip: truebackground: Rectangle { color: "#F1F1F1" }TextArea {id: textAreabackground: Rectangle { color: "#F1F1F1" }font.pixelSize: 20selectByMouse: trueselectionColor: "#87cefa"leftPadding: 0SyntaxHighlighter {id: highlighterdocument: textArea.textDocumentlanguage: SyntaxHighlighter.CPPonLanguageChanged: {var data = textArea.texttextArea.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;}}