Ebatsin
Ebatsin

Reputation: 581

QSyntaxHighlighter and multiline comments

I am using Qt's QSyntaxHighlighter to color some C like syntax in a QML TextEdit

Everything works great except for multiline comments.

I am detecting them this way :

void highlightBlock(QString const& text) override {
    bool inMultilineComment = previousBlockState() == STATES::COMMENT;
    bool inSingleLineComment = false;
    int previousIndex = 0;

    QRegularExpression expr("(\\/\\*|\\*\\/|\\/\\/|\n)"); // will match either /**, /**, // or \n
    QRegularExpressionMatchIterator it = expr.globalMatch(text);

    while(it.hasNext()) {
        QRegularExpressionMatch match = it.next();

        const QString captured = match.captured(1);

        if(captured == "/*" && !inSingleLineComment) {
            inMultilineComment = true;
            previousIndex = match.capturedStart(1);
        }

        if(captured == "*/" && inMultilineComment) {
            inMultilineComment = false;
            setFormat(previousIndex, match.capturedEnd(1) - previousIndex, _commentFormat);
        }

        if(captured == "//" && !inMultilineComment) {
            inSingleLineComment = true;
        }

        if(captured == "\n" && inSingleLineComment) {
            inSingleLineComment = false;
        }
    }

    if(inMultilineComment) {
        setFormat(previousIndex, text.size() - previousIndex, _commentFormat);
        setCurrentBlockState(STATES::COMMENT);
    }
    else {
        setCurrentBlockState(STATES::NONE);
    }
}

It works until I take a multiline comment already colored and I remove the /* at the begining. Only the block that contains the /* is processed and recolored, but not the following ones, which means that they continue to appear commented when they are not.

Is there an easy way to tell QSyntaxHighlighter to re-process the following blocks to prevent such mis-colorations ?

Upvotes: 3

Views: 777

Answers (1)

Jesse C
Jesse C

Reputation: 839

I ran into this same problem recently and discovered that Qt actually should be handling this for you, assuming that you set your blockState correctly.

If you look at the sourceCode for QSyntaxHighlighterPrivate::reformatBlocks in the Qt5 source code, you'll see

while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
    const int stateBeforeHighlight = block.userState();
    reformatBlock(block);
    forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight);
    block = block.next();
}

retrieved from https://code.woboq.org/qt5/qtbase/src/gui/text/qsyntaxhighlighter.cpp.html#165

That code (which is fired by a contentsChange signal from the QTextDocument your highlighter is on) will iterate through each block (line) starting from the block that was just modified. Assuming that the state of the block changed based on the typing change that just happened, it will continue to process the following blocks. This means that you need to get your userState correct for every line and Qt should handle the rest.

Given the example

    /*
     * This is a comment
     *
     * That I made
     */

You would want to start in the condition where every line had the STATES::COMMENT set except for the last line which should be set to STATES::NONE. Once you do something like deleting the initial /* you need to make sure that the block state is reset to STATES::NONE. That will trigger Qt to rerun the next block, which will also need to change its state, etc.

In my (python) code, I ended up using a combination of print statements and real debugging to track the propagation of state changes and figured out where it was not correctly updating and breaking the chain of updates. Your code looks superficially correct, though I did not try to compile and run it, but I suspect there is a some case being triggered where the state is not being updated correctly after an edit.

Upvotes: 1

Related Questions