Qt5 Syntax Highlighting in QML

后端 未结 5 1546
谎友^
谎友^ 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 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: "

    x = 123;

    y = 456;

    z = x + y;

    " 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 "" + f + ""; if (f.match(/^[a-z][A-Za-z]*$/)) return "" + f + ""; else if (f.match(/^[0-9]+$/)) return "" + f + ""; else if (f.match(/^[ ]/)) return " " else if (f.match(/^[\t\n]/)) return f; else if (f.match(/^[']/)) return "" + f + ""; 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" }
    

提交回复
热议问题