Qt5 Syntax Highlighting in QML

后端 未结 5 1547
谎友^
谎友^ 2021-02-05 10:38

I am working on a QtQuick 2.0 presentation and I would like to embed some code samples. is it possible easily to create a syntax highlighting qml element.

C

相关标签:
5条回答
  • 2021-02-05 10:55

    Take a look at QSyntaxHighlighter.

    If you need a QML Item doing syntax highlighting, you can simply create your own by extending QDeclarativeItem and using the utility above.

    0 讨论(0)
  • 2021-02-05 11:02

    Qt Quick's TextEdit item exposes a textDocument property, of type QQuickTextDocument. This is explicitly exposed so you can use QSyntaxHighlighter directly with the document.

    QtQuick textEdit documentation for Qt 5.3

    0 讨论(0)
  • 2021-02-05 11:03

    in your app file:

    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;
    

    The template function to get child objects (returns the first occurence of objectName, so use unique names to identify objects in your qml files) :

    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;
    }
    

    in your qml file use a TextEdit (inside a Flickable or whatever you want) with the objectName property correctly set:

    .... 
    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)
    }
    ....
    
    
    0 讨论(0)
  • 2021-02-05 11:18

    I have two answers:

    1. a pure QML answer
    2. a C++ answer involving QSyntaxHighlighter

    For a pure QML answer, we can use a TextArea one can use textFormat: TextEdit.RichText for formatting. We can use TextArea::getText() to get the plain text and set TextArea::text with the rich text. Here's a mock example that:

    • uppercase identifiers (e.g. Button) into purple
    • lowercase identifiers (e.g. x y z) into red
    • turns numbers (e.g. 123 456) into blue
    • and symbols (e.g. = + ;) stay black

    Here's the snippet:

        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;
                    var p = cursorPosition;
                    var mu = getText(0, length);
                    mu = mu.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 = mu;
                    cursorPosition = p;
                    processing = false;
                }
            }
        }
    

    To use Qt's QSyntaxHighlighter, you need the following:

    1. In QML, use TextEdit QML type in your application
    2. In C++, define a QSyntaxHighlighter and connect TextEdit QML type to it via the textDocument property
    3. In C++, implement QSyntaxHighlighter::highlightBlock( const QString& text ) in your derived class, calling setFormat() as often as needed to tokenize the text found.

    To make things easier, I create a sample app https://github.com/stephenquan/QtSyntaxHighlighterApp which wraps QSyntaxHighlighter and QTextFormat as SyntaxHighlighter and TextFormat QML Types. That way, one can handle onHighlightBlock signal and put the business logic of the syntax highlighter in Javascript instead of 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" }
    
    0 讨论(0)
  • 2021-02-05 11:19

    There is no obvious way to achieve syntax highlighting in QML.

    One could implement one's own declarative item, performing the actual highlighting with QSyntaxHighlighter but then one would have to define its own highlighting rules for language of the source code in question. I would't do that amount of coding for a presentation.

    Instead I would display the code in a WebView item with the highlighting already applied as static HTML markup or with the help of a JavaScript highlighting library, for expample highlight.js.

    Update 1

    If the WebView item is indeed unusable, even the simple Text item with its rudimentary HTML support should be enough to handle the source code highlighting usecase if fed with static HTML.

    0 讨论(0)
提交回复
热议问题