highlighter.cpp 24.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
25
26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30
31
32
33
34
35
36
37

#include "highlighter.h"
#include "highlightdefinition.h"
#include "context.h"
#include "rule.h"
#include "itemdata.h"
#include "highlighterexception.h"
#include "progressdata.h"
38
#include "reuse.h"
39
#include "tabsettings.h"
40

41
42
43
#include <coreplugin/messagemanager.h>

#include <QCoreApplication>
44

45
using namespace TextEditor;
46
47
48
49
50
51
52
53
54
using namespace Internal;

namespace {
    static const QLatin1String kStay("#stay");
    static const QLatin1String kPop("#pop");
    static const QLatin1Char kBackSlash('\\');
    static const QLatin1Char kHash('#');
}

55
56
57
class HighlighterCodeFormatterData : public CodeFormatterData
{
public:
58
59
60
61
62
63
    HighlighterCodeFormatterData() :
        m_foldingIndentDelta(0),
        m_originalObservableState(-1),
        m_continueObservableState(-1)
    {}

64
65
66
67
    ~HighlighterCodeFormatterData() {}
    int m_foldingIndentDelta;
    int m_originalObservableState;
    QStack<QString> m_foldingRegions;
68
    int m_continueObservableState;
69
70
71
72
73
};

HighlighterCodeFormatterData *formatterData(const QTextBlock &block)
{
    HighlighterCodeFormatterData *data = 0;
74
    if (TextBlockUserData *userData = TextDocumentLayout::userData(block)) {
75
76
77
78
79
80
81
82
83
        data = static_cast<HighlighterCodeFormatterData *>(userData->codeFormatterData());
        if (!data) {
            data = new HighlighterCodeFormatterData;
            userData->setCodeFormatterData(data);
        }
    }
    return data;
}

84
Highlighter::Highlighter(QTextDocument *parent) :
85
    SyntaxHighlighter(parent),
86
    m_regionDepth(0),
87
88
    m_indentationBasedFolding(false),
    m_tabSettings(0),
89
    m_persistentObservableStatesCounter(PersistentsStart),
90
    m_dynamicContextsCounter(0),
91
    m_isBroken(false)
92
{
93
    static QVector<TextStyle> categories;
94
    if (categories.isEmpty()) {
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        categories << C_TEXT
                   << C_VISUAL_WHITESPACE
                   << C_KEYWORD
                   << C_TYPE
                   << C_COMMENT
                   << C_NUMBER
                   << C_NUMBER
                   << C_NUMBER
                   << C_STRING
                   << C_STRING
                   << C_TEXT // TODO : add style for alert (eg. yellow background)
                   << C_TEXT // TODO : add style for error (eg. red underline)
                   << C_FUNCTION
                   << C_TEXT
                   << C_TEXT
                   << C_LOCAL;
111
112
113
114
    }

    setTextFormatCategories(categories);
}
115
116
117
118

Highlighter::~Highlighter()
{}

119
120
121
122
123
124
125
126
// Mapping from Kate format strings to format ids.
struct KateFormatMap
{
    KateFormatMap();
    QHash<QString, Highlighter::TextFormatId> m_ids;
};

KateFormatMap::KateFormatMap()
127
128
129
130
131
132
133
134
135
136
137
{
    m_ids.insert(QLatin1String("dsNormal"), Highlighter::Normal);
    m_ids.insert(QLatin1String("dsKeyword"), Highlighter::Keyword);
    m_ids.insert(QLatin1String("dsDataType"), Highlighter::DataType);
    m_ids.insert(QLatin1String("dsDecVal"), Highlighter::Decimal);
    m_ids.insert(QLatin1String("dsBaseN"), Highlighter::BaseN);
    m_ids.insert(QLatin1String("dsFloat"), Highlighter::Float);
    m_ids.insert(QLatin1String("dsChar"), Highlighter::Char);
    m_ids.insert(QLatin1String("dsString"), Highlighter::String);
    m_ids.insert(QLatin1String("dsComment"), Highlighter::Comment);
    m_ids.insert(QLatin1String("dsAlert"), Highlighter::Alert);
138
    m_ids.insert(QLatin1String("dsError"), Highlighter::Error);
139
140
    m_ids.insert(QLatin1String("dsFunction"), Highlighter::Function);
    m_ids.insert(QLatin1String("dsRegionMarker"), Highlighter::RegionMarker);
141
    m_ids.insert(QLatin1String("dsOthers"), Highlighter::Others);
142
    m_ids.insert(QLatin1String("dsIdentifier"), Highlighter::Identifier);
143
}
144

145
146
Q_GLOBAL_STATIC(KateFormatMap, kateFormatMap)

147
void Highlighter::setDefaultContext(const QSharedPointer<Context> &defaultContext)
148
149
{
    m_defaultContext = defaultContext;
150
    m_persistentObservableStates.insert(m_defaultContext->name(), Default);
151
152
153
154
155
156
    m_indentationBasedFolding = defaultContext->definition()->isIndentationBasedFolding();
}

void Highlighter::setTabSettings(const TabSettings &ts)
{
    m_tabSettings = &ts;
157
}
158

159
160
161
162
163
164
165
166
167
168
static bool isOpeningParenthesis(QChar c)
{
    return c == QLatin1Char('{') || c == QLatin1Char('[') || c == QLatin1Char('(');
}

static bool isClosingParenthesis(QChar c)
{
    return c == QLatin1Char('}') || c == QLatin1Char(']') || c == QLatin1Char(')');
}

169
170
171
172
173
void Highlighter::highlightBlock(const QString &text)
{
    if (!m_defaultContext.isNull() && !m_isBroken) {
        try {
            setupDataForBlock(text);
174

175
176
            handleContextChange(m_currentContext->lineBeginContext(),
                                m_currentContext->definition());
177

178
179
            ProgressData progress;
            const int length = text.length();
180
            while (progress.offset() < length)
181
                iterateThroughRules(text, length, &progress, false, m_currentContext->rules());
182

183
184
185
186
187
            if (extractObservableState(currentBlockState()) != WillContinue) {
                handleContextChange(m_currentContext->lineEndContext(),
                                    m_currentContext->definition(),
                                    false);
            }
188
            m_contexts.clear();
189

190
191
192
193
            if (m_indentationBasedFolding) {
                applyIndentationBasedFolding(text);
            } else {
                applyRegionBasedFolding();
194

195
196
197
                // In the case region depth has changed since the last time the state was set.
                setCurrentBlockState(computeState(extractObservableState(currentBlockState())));
            }
198

199
            Parentheses parentheses;
200
201
202
203
204
205
206
207
208
            for (int pos = 0; pos < length; ++pos) {
                const QChar c = text.at(pos);
                if (isOpeningParenthesis(c))
                    parentheses.push_back(Parenthesis(Parenthesis::Opened, c, pos));
                else if (isClosingParenthesis(c))
                    parentheses.push_back(Parenthesis(Parenthesis::Closed, c, pos));
            }
            TextDocumentLayout::setParentheses(currentBlock(), parentheses);

209
210
211
212
213
        } catch (const HighlighterException &e) {
            Core::MessageManager::write(
                        QCoreApplication::translate("GenericHighlighter",
                                                    "Generic highlighter error: ") + e.message(),
                        Core::MessageManager::WithFocus);
214
            m_isBroken = true;
215
216
        }
    }
217

218
    applyFormatToSpaces(text, formatForCategory(VisualWhitespace));
219
220
221
222
}

void Highlighter::setupDataForBlock(const QString &text)
{
223
    if (extractObservableState(currentBlockState()) == WillContinue)
224
225
        analyseConsistencyOfWillContinueBlock(text);

226
227
    if (previousBlockState() == -1) {
        m_regionDepth = 0;
228
        setupDefault();
229
230
231
232
233
234
235
236
237
238
239
240
    } else {
        m_regionDepth = extractRegionDepth(previousBlockState());
        const int observablePreviousState = extractObservableState(previousBlockState());
        if (observablePreviousState == Default)
            setupDefault();
        else if (observablePreviousState == WillContinue)
            setupFromWillContinue();
        else if (observablePreviousState == Continued)
            setupFromContinued();
        else
            setupFromPersistent();

241
242
        formatterData(currentBlock())->m_foldingRegions =
                formatterData(currentBlock().previous())->m_foldingRegions;
243
244
245
    }

    assignCurrentContext();
246
247
248
249
250
251
}

void Highlighter::setupDefault()
{
    m_contexts.push_back(m_defaultContext);

252
    setCurrentBlockState(computeState(Default));
253
254
255
256
}

void Highlighter::setupFromWillContinue()
{
257
    HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
258
    pushContextSequence(previousData->m_continueObservableState);
259

260
    HighlighterCodeFormatterData *data = formatterData(currentBlock());
261
    data->m_originalObservableState = previousData->m_originalObservableState;
262

263
264
    if (currentBlockState() == -1 || extractObservableState(currentBlockState()) == Default)
        setCurrentBlockState(computeState(Continued));
265
266
267
268
}

void Highlighter::setupFromContinued()
{
269
    HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
270

271
272
    Q_ASSERT(previousData->m_originalObservableState != WillContinue &&
             previousData->m_originalObservableState != Continued);
273

274
275
    if (previousData->m_originalObservableState == Default ||
        previousData->m_originalObservableState == -1) {
276
        m_contexts.push_back(m_defaultContext);
277
278
279
    } else {
        pushContextSequence(previousData->m_originalObservableState);
    }
280

281
    setCurrentBlockState(computeState(previousData->m_originalObservableState));
282
283
284
285
}

void Highlighter::setupFromPersistent()
{
286
    pushContextSequence(extractObservableState(previousBlockState()));
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309

    setCurrentBlockState(previousBlockState());
}

void Highlighter::iterateThroughRules(const QString &text,
                                      const int length,
                                      ProgressData *progress,
                                      const bool childRule,
                                      const QList<QSharedPointer<Rule> > &rules)
{
    typedef QList<QSharedPointer<Rule> >::const_iterator RuleIterator;

    bool contextChanged = false;
    bool atLeastOneMatch = false;

    RuleIterator it = rules.begin();
    RuleIterator endIt = rules.end();
    while (it != endIt && progress->offset() < length) {
        int startOffset = progress->offset();
        const QSharedPointer<Rule> &rule = *it;
        if (rule->matchSucceed(text, length, progress)) {
            atLeastOneMatch = true;

310
311
            if (!m_indentationBasedFolding) {
                if (!rule->beginRegion().isEmpty()) {
312
                    formatterData(currentBlock())->m_foldingRegions.push(rule->beginRegion());
313
314
                    ++m_regionDepth;
                    if (progress->isOpeningBraceMatchAtFirstNonSpace())
315
                        ++formatterData(currentBlock())->m_foldingIndentDelta;
316
                }
317
318
                if (!rule->endRegion().isEmpty()) {
                    QStack<QString> *currentRegions =
319
                        &formatterData(currentBlock())->m_foldingRegions;
320
321
322
323
                    if (!currentRegions->isEmpty() && rule->endRegion() == currentRegions->top()) {
                        currentRegions->pop();
                        --m_regionDepth;
                        if (progress->isClosingBraceMatchAtNonEnd())
324
                            --formatterData(currentBlock())->m_foldingIndentDelta;
325
326
327
                    }
                }
                progress->clearBracesMatches();
328
329
            }

330
            if (progress->isWillContinueLine()) {
331
332
333
                createWillContinueBlock();
                progress->setWillContinueLine(false);
            } else {
Leandro Melo's avatar
Leandro Melo committed
334
335
                if (rule->hasChildren())
                    iterateThroughRules(text, length, progress, true, rule->children());
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374

                if (!rule->context().isEmpty() && contextChangeRequired(rule->context())) {
                    m_currentCaptures = progress->captures();
                    changeContext(rule->context(), rule->definition());
                    contextChanged = true;
                }
            }

            // Format is not applied to child rules directly (but relative to the offset of their
            // parent) nor to look ahead rules.
            if (!childRule && !rule->isLookAhead()) {
                if (rule->itemData().isEmpty())
                    applyFormat(startOffset, progress->offset() - startOffset,
                                m_currentContext->itemData(), m_currentContext->definition());
                else
                    applyFormat(startOffset, progress->offset() - startOffset, rule->itemData(),
                                rule->definition());
            }

            // When there is a match of one child rule the others should be skipped. Otherwise
            // the highlighting would be incorret in a case like 9ULLLULLLUULLULLUL, for example.
            if (contextChanged || childRule) {
                break;
            } else {
                it = rules.begin();
                continue;
            }
        }
        ++it;
    }

    if (!childRule && !atLeastOneMatch) {
        if (m_currentContext->isFallthrough()) {
            handleContextChange(m_currentContext->fallthroughContext(),
                                m_currentContext->definition());
            iterateThroughRules(text, length, progress, false, m_currentContext->rules());
        } else {
            applyFormat(progress->offset(), 1, m_currentContext->itemData(),
                        m_currentContext->definition());
375
            if (progress->isOnlySpacesSoFar() && !text.at(progress->offset()).isSpace())
376
                progress->setOnlySpacesSoFar(false);
377
378
379
380
381
382
383
384
385
386
387
388
389
390
            progress->incrementOffset();
        }
    }
}

bool Highlighter::contextChangeRequired(const QString &contextName) const
{
    if (contextName == kStay)
        return false;
    return true;
}

void Highlighter::changeContext(const QString &contextName,
                                const QSharedPointer<HighlightDefinition> &definition,
391
                                const bool assignCurrent)
392
393
394
{
    if (contextName.startsWith(kPop)) {
        QStringList list = contextName.split(kHash, QString::SkipEmptyParts);
395
        for (int i = 0; i < list.size(); ++i) {
396
397
398
399
            if (m_contexts.isEmpty()) {
                throw HighlighterException(
                        QCoreApplication::translate("GenericHighlighter", "Reached empty context"));
            }
400
            m_contexts.pop_back();
401
        }
402

403
        if (extractObservableState(currentBlockState()) >= PersistentsStart) {
404
            // One or more contexts were popped during during a persistent state.
405
            const QString &currentSequence = currentContextSequence();
406
407
408
            if (m_persistentObservableStates.contains(currentSequence))
                setCurrentBlockState(
                    computeState(m_persistentObservableStates.value(currentSequence)));
409
            else
410
411
                setCurrentBlockState(
                    computeState(m_leadingObservableStates.value(currentSequence)));
412
413
414
415
416
417
418
419
420
        }
    } else {
        const QSharedPointer<Context> &context = definition->context(contextName);

        if (context->isDynamic())
            pushDynamicContext(context);
        else
            m_contexts.push_back(context);

421
        if (m_contexts.back()->lineEndContext() == kStay ||
422
            extractObservableState(currentBlockState()) >= PersistentsStart) {
423
            const QString &currentSequence = currentContextSequence();
424
425
426
427
            mapLeadingSequence(currentSequence);
            if (m_contexts.back()->lineEndContext() == kStay) {
                // A persistent context was pushed.
                mapPersistentSequence(currentSequence);
428
429
                setCurrentBlockState(
                    computeState(m_persistentObservableStates.value(currentSequence)));
430
            }
431
432
433
        }
    }

434
435
    if (assignCurrent)
        assignCurrentContext();
436
437
438
439
440
441
442
443
444
445
446
447
}

void Highlighter::handleContextChange(const QString &contextName,
                                      const QSharedPointer<HighlightDefinition> &definition,
                                      const bool setCurrent)
{
    if (!contextName.isEmpty() && contextChangeRequired(contextName))
        changeContext(contextName, definition, setCurrent);
}

void Highlighter::applyFormat(int offset,
                              int count,
448
                              const QString &itemDataName,
449
450
451
452
453
                              const QSharedPointer<HighlightDefinition> &definition)
{
    if (count == 0)
        return;

454
    QSharedPointer<ItemData> itemData;
455
    try {
456
        itemData = definition->itemData(itemDataName);
457
    } catch (const HighlighterException &) {
458
459
        // There are some broken files. For instance, the Printf context in java.xml points to an
        // inexistent Printf item data. These cases are considered to have normal text style.
460
461
462
        return;
    }

463
    TextFormatId formatId = kateFormatMap()->m_ids.value(itemData->style(), Normal);
464
    if (formatId != Normal) {
465
466
467
        QTextCharFormat format = formatForCategory(formatId);
        if (itemData->isCustomized()) {
            // Please notice that the following are applied every time for item data which have
468
            // customizations. The configureFormats function could be used to provide a "one time"
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
            // configuration, but it would probably require to traverse all item data from all
            // definitions available/loaded (either to set the values or for some "notifying"
            // strategy). This is because the highlighter does not really know on which
            // definition(s) it is working. Since not many item data specify customizations I
            // think this approach would fit better. If there are other ideas...
            if (itemData->color().isValid())
                format.setForeground(itemData->color());
            if (itemData->isItalicSpecified())
                format.setFontItalic(itemData->isItalic());
            if (itemData->isBoldSpecified())
                format.setFontWeight(toFontWeight(itemData->isBold()));
            if (itemData->isUnderlinedSpecified())
                format.setFontUnderline(itemData->isUnderlined());
            if (itemData->isStrikeOutSpecified())
                format.setFontStrikeOut(itemData->isStrikeOut());
484
        }
485
486

        setFormat(offset, count, format);
487
    }
488
489
}

490
491
void Highlighter::createWillContinueBlock()
{
492
    HighlighterCodeFormatterData *data = formatterData(currentBlock());
493
494
    const int currentObservableState = extractObservableState(currentBlockState());
    if (currentObservableState == Continued) {
495
        HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
496
497
498
        data->m_originalObservableState = previousData->m_originalObservableState;
    } else if (currentObservableState != WillContinue) {
        data->m_originalObservableState = currentObservableState;
499
    }
500
501
502
503
    const QString currentSequence = currentContextSequence();
    mapPersistentSequence(currentSequence);
    data->m_continueObservableState = m_persistentObservableStates.value(currentSequence);
    m_persistentContexts.insert(data->m_continueObservableState, m_contexts);
504

505
    setCurrentBlockState(computeState(WillContinue));
506
507
508
509
510
511
}

void Highlighter::analyseConsistencyOfWillContinueBlock(const QString &text)
{
    if (currentBlock().next().isValid() && (
        text.length() == 0 || text.at(text.length() - 1) != kBackSlash) &&
512
513
        extractObservableState(currentBlock().next().userState()) != Continued) {
        currentBlock().next().setUserState(computeState(Continued));
514
515
516
    }

    if (text.length() == 0 || text.at(text.length() - 1) != kBackSlash) {
517
        HighlighterCodeFormatterData *data = formatterData(currentBlock());
518
        setCurrentBlockState(computeState(data->m_originalObservableState));
519
520
521
    }
}

522
void Highlighter::mapPersistentSequence(const QString &contextSequence)
523
{
524
525
526
    if (!m_persistentObservableStates.contains(contextSequence)) {
        int newState = m_persistentObservableStatesCounter;
        m_persistentObservableStates.insert(contextSequence, newState);
527
        m_persistentContexts.insert(newState, m_contexts);
528
        ++m_persistentObservableStatesCounter;
529
530
531
    }
}

532
533
void Highlighter::mapLeadingSequence(const QString &contextSequence)
{
534
535
536
    if (!m_leadingObservableStates.contains(contextSequence))
        m_leadingObservableStates.insert(contextSequence,
                                         extractObservableState(currentBlockState()));
537
538
}

539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
void Highlighter::pushContextSequence(int state)
{
    const QVector<QSharedPointer<Context> > &contexts = m_persistentContexts.value(state);
    for (int i = 0; i < contexts.size(); ++i)
        m_contexts.push_back(contexts.at(i));
}

QString Highlighter::currentContextSequence() const
{
    QString sequence;
    for (int i = 0; i < m_contexts.size(); ++i)
        sequence.append(m_contexts.at(i)->id());

    return sequence;
}

void Highlighter::pushDynamicContext(const QSharedPointer<Context> &baseContext)
{
    // A dynamic context is created from another context which serves as its basis. Then,
    // its rules are updated according to the captures from the calling regular expression which
    // triggered the push of the dynamic context.
    QSharedPointer<Context> context(new Context(*baseContext));
    context->configureId(m_dynamicContextsCounter);
    context->updateDynamicRules(m_currentCaptures);
    m_contexts.push_back(context);
    ++m_dynamicContextsCounter;
}

567
void Highlighter::assignCurrentContext()
568
{
569
    if (m_contexts.isEmpty()) {
570
571
572
        // This is not supposed to happen. However, there are broken files (for example, php.xml)
        // which will cause this behaviour. In such cases pushing the default context is enough to
        // keep highlighter working.
573
574
        m_contexts.push_back(m_defaultContext);
    }
575
576
    m_currentContext = m_contexts.back();
}
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592

int Highlighter::extractRegionDepth(const int state)
{
    return state >> 12;
}

int Highlighter::extractObservableState(const int state)
{
    return state & 0xFFF;
}

int Highlighter::computeState(const int observableState) const
{
    return m_regionDepth << 12 | observableState;
}

593
void Highlighter::applyRegionBasedFolding() const
594
595
{
    int folding = 0;
596
    TextBlockUserData *currentBlockUserData = TextDocumentLayout::userData(currentBlock());
597
598
    HighlighterCodeFormatterData *data = formatterData(currentBlock());
    HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
599
600
601
602
603
    if (previousData) {
        folding = extractRegionDepth(previousBlockState());
        if (data->m_foldingIndentDelta != 0) {
            folding += data->m_foldingIndentDelta;
            if (data->m_foldingIndentDelta > 0)
604
                currentBlockUserData->setFoldingStartIncluded(true);
605
            else
606
                TextDocumentLayout::userData(currentBlock().previous())->setFoldingEndIncluded(false);
607
608
609
            data->m_foldingIndentDelta = 0;
        }
    }
610
611
    currentBlockUserData->setFoldingEndIncluded(true);
    currentBlockUserData->setFoldingIndent(folding);
612
}
613
614
615

void Highlighter::applyIndentationBasedFolding(const QString &text) const
{
616
    TextBlockUserData *data = TextDocumentLayout::userData(currentBlock());
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
    data->setFoldingEndIncluded(true);

    // If this line is empty, check its neighbours. They all might be part of the same block.
    if (text.trimmed().isEmpty()) {
        data->setFoldingIndent(0);
        const int previousIndent = neighbouringNonEmptyBlockIndent(currentBlock().previous(), true);
        if (previousIndent > 0) {
            const int nextIndent = neighbouringNonEmptyBlockIndent(currentBlock().next(), false);
            if (previousIndent == nextIndent)
                data->setFoldingIndent(previousIndent);
        }
    } else {
        data->setFoldingIndent(m_tabSettings->indentationColumn(text));
    }
}

int Highlighter::neighbouringNonEmptyBlockIndent(QTextBlock block, const bool previous) const
{
    while (true) {
        if (!block.isValid())
            return 0;
        if (block.text().trimmed().isEmpty()) {
            if (previous)
                block = block.previous();
            else
                block = block.next();
        } else {
            return m_tabSettings->indentationColumn(block.text());
        }
    }
}