在QML中使用Qt5的语法高亮功能

15
我正在制作一个关于 QtQuick 2.0 的演示文稿,想要嵌入一些代码示例。是否可以轻松创建一个语法高亮的 QML 元素呢?
所要达到的目标是在 TextEditTextArea 中应用语法高亮。我知道 Qt 有一个 QSyntaxHighlighter,但是文档并不清楚如何将其应用于 TextEditTextArea
你能提供一些如何实现的示例吗?

能否请您举例说明@JamesTurner的答案如何为您带来解决方案?我无法在Qt 5.6中使用TextArea实现该功能。 - Victor Polevoy
@VictorPolevoy 不好意思,我没有任何代码可以与您分享。您有什么问题需要帮忙吗? - Itay Grudev
我发现"selectionColor"是一个很好的属性。https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea-members.html - Tyler M
5个回答

10

Qt Quick的TextEdit项公开了一个类型为QQuickTextDocumenttextDocument属性。这是明确公开的,以便您可以直接使用QSyntaxHighlighter对该文档进行语法高亮。

Qt 5.3的QtQuick textEdit文档


10
我有两个答案:
  1. 一个纯QML的答案
  2. 一个涉及QSyntaxHighlighter的C++答案
对于纯QML答案,我们可以使用TextArea,并将TextArea::textFormat设置为textFormat: TextEdit.RichText以进行格式化。我们可以使用TextArea::getText()获取纯文本,并使用富文本设置TextArea::text。这是一个模拟示例:
  • 将大写标识符(例如 Button)标记为紫色
  • 将小写标识符(例如 x y z)标记为红色
  • 将数字(例如 123 456)标记为蓝色
  • 但保留符号(例如 = + ;) 为黑色

这是代码片段:

    TextArea {
        id: output

        property bool processing: false

        text: "<p>x = 123;</p><p>y = 456;</p><p>z = x + y;</p>"
        textFormat: TextEdit.RichText
        selectByMouse: true

        onTextChanged: {
            if (!processing) {
                processing = true;
                let p = cursorPosition;
                let markUp = getText(0, length).replace(
                  /([A-Z][A-Za-z]*|[a-z][A-Za-z]*|[0-9]+|[ \t\n]|['][^']*[']|[^A-Za-z0-9\t\n ])/g,
                    function (f) {
                        console.log("f: ", JSON.stringify(f));
                        if (f.match(/^[A-Z][A-Za-z]*$/))
                            return "<span style='color:#800080'>" + f + "</span>";
                        if (f.match(/^[a-z][A-Za-z]*$/))
                            return "<span style='color:#800000'>" + f + "</span>";
                        else if (f.match(/^[0-9]+$/))
                            return "<span style='color:#0000ff'>" + f + "</span>";
                        else if (f.match(/^[ ]/))
                            return "&nbsp;"
                        else if (f.match(/^[\t\n]/))
                            return f;
                        else if (f.match(/^[']/))
                            return "<span style='color:#008000'>" + f + "</span>";
                        else
                            return f;
                    }
                );
                text = markUp;
                cursorPosition = p;
                processing = false;
            }
        }
    }

要使用Qt的QSyntaxHighlighter,您需要以下内容:

  1. 在QML中,在您的应用程序中使用TextEdit QML类型
  2. 在C++中,定义一个QSyntaxHighlighter并通过TextEdit::textDocument属性将TextEdit QML类型连接到它
  3. 在C++中,实现您的派生类中的QSyntaxHighlighter::highlightBlock(const QString& text),根据需要多次调用QSyntaxHighlighter::setFormat()来分词找到的文本。
为了让事情更简单,我创建了一个示例应用程序https://github.com/stephenquan/QtSyntaxHighlighterApp,它将QSyntaxHighlighterQTextFormat封装为SyntaxHighlighterTextFormat QML类型。这样,可以处理onHighlightBlock信号,并将语法高亮器的业务逻辑放在JavaScript中,而不是C++中:
TextEdit {
    id: textEdit
    selectByMouse: true
    text: [
        "import QtQuick 2.12",
        "",
        "Item {",
        "    Rectangle {",
        "        width: 50",
        "        height: 50",
        "        color: '#800000'",
        "    }",
        "}",
    ].join("\n") + "\n"
    font.pointSize: 12
}

SyntaxHighlighter {
    id: syntaxHighlighter
    textDocument: textEdit.textDocument
    onHighlightBlock: {
        let rx = /\/\/.*|[A-Za-z.]+(\s*:)?|\d+(.\d*)?|'[^']*?'|"[^"]*?"/g;
        let m;
        while ( ( m = rx.exec(text) ) !== null ) {
            if (m[0].match(/^\/\/.*/)) {
                setFormat(m.index, m[0].length, commentFormat);
                continue;
            }
            if (m[0].match(/^[a-z][A-Za-z.]*\s*:/)) {
                setFormat(m.index, m[0].match(/^[a-z][A-Za-z.]*/)[0].length, propertyFormat);
                continue;
            }
            if (m[0].match(/^[a-z]/)) {
                let keywords = [ 'import', 'function', 'bool', 'var',
                                'int', 'string', 'let', 'const', 'property',
                                'if', 'continue', 'for', 'break', 'while',
                    ];
                if (keywords.includes(m[0])) {
                    setFormat(m.index, m[0].length, keywordFormat);
                    continue;
                }
                continue;
            }
            if (m[0].match(/^[A-Z]/)) {
                setFormat(m.index, m[0].length, componentFormat);
                continue;
            }
            if (m[0].match(/^\d/)) {
                setFormat(m.index, m[0].length, numberFormat);
                continue;
            }
            if (m[0].match(/^'/)) {
                setFormat(m.index, m[0].length, stringFormat);
                continue;
            }
            if (m[0].match(/^"/)) {
                setFormat(m.index, m[0].length, stringFormat);
                continue;
            }
        }
    }
}

TextCharFormat { id: keywordFormat; foreground: "#808000" }
TextCharFormat { id: componentFormat; foreground: "#aa00aa"; font.pointSize: 12; font.bold: true; font.italic: true }
TextCharFormat { id: numberFormat; foreground: "#0055af" }
TextCharFormat { id: propertyFormat; foreground: "#800000" }
TextCharFormat { id: stringFormat; foreground: "green" }
TextCharFormat { id: commentFormat; foreground: "green" }

8

在QML中没有明显的方法来实现语法高亮。

可以实现自己声明的项,使用QSyntaxHighlighter执行实际的高亮,但是需要为所涉及的源代码语言定义自己的高亮规则。我不会为了演示而编写那么多代码。

相反,我会在WebView项目中显示已应用高亮的静态HTML标记或使用JavaScript高亮库,例如highlight.js

更新1

如果WebView项目确实无法使用,即使是简单的Text项目,只要提供静态HTML,其基本HTML支持应该足以处理源代码高亮显示用例。

这个想法在我脑海中浮现,但不幸的是,QtQuick2 WebView 元素具有一些移动设备大小优化,这些优化与应该动态应用大小的演示文稿并不兼容。(全屏) - Itay Grudev

5
在您的应用文件中:
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQuickTextDocument* doc = childObject<QQuickTextDocument*>(engine, "textEditor", "textDocument");
Q_ASSERT(doc != 0);

// QSyntaxHighlighter derrived class
MySyntaxHighlighter* parser = new MySyntaxHighlighter(doc->textDocument());
// use parser, see QSyntaxHighlighter doc...
int ret = app.exec();
delete parser;
return ret;

获取子对象的模板函数(返回objectName的第一个匹配项,因此在qml文件中使用唯一名称来标识对象):

template <class T> T childObject(QQmlApplicationEngine& engine,
                                 const QString& objectName,
                                 const QString& propertyName)
{
    QList<QObject*> rootObjects = engine.rootObjects();
    foreach (QObject* object, rootObjects)
    {
        QObject* child = object->findChild<QObject*>(objectName);
        if (child != 0)
        {
            std::string s = propertyName.toStdString();
            QObject* object = child->property(s.c_str()).value<QObject*>();
            Q_ASSERT(object != 0);
            T prop = dynamic_cast<T>(object);
            Q_ASSERT(prop != 0);
            return prop;
        }
    }
    return (T) 0;
}

在您的 QML 文件中,使用一个已正确设置 objectName 属性的 TextEdit(放在 Flickable 或其他您想要的控件内部)。
.... 
TextEdit {
    id: edit
    objectName: "textEditor"
    width: flick.width
    height: flick.height
    focus: true
    font.family: "Courier New"
    font.pointSize: 12
    wrapMode: TextEdit.NoWrap
    onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
}
....


这个不起作用是因为QSyntaxHighlighter是一个抽象类。 - nocnokneo
你是正确的。使用一个派生自QSyntaxHighlighter的类。 - Bertrand

3

请看QSyntaxHighlighter

如果需要实现 QML 语法高亮,您可以通过扩展 QDeclarativeItem 并使用上述工具来轻松创建自己的项目。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接