Files
qt-creator/src/plugins/texteditor/generichighlighter/highlighter.cpp
Eike Ziller 6ea686faa9 Avoid accessing global font settings from syntax highlighters
Change-Id: Ic015f7449351166ec41a7745355ddc63504f6442
Reviewed-by: André Hartmann <aha_1980@gmx.de>
Reviewed-by: David Schulz <david.schulz@qt.io>
2018-09-19 06:36:55 +00:00

747 lines
29 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "context.h"
#include "highlightdefinition.h"
#include "highlighter.h"
#include "highlighterexception.h"
#include "itemdata.h"
#include "progressdata.h"
#include "reuse.h"
#include "rule.h"
#include "tabsettings.h"
#include <coreplugin/messagemanager.h>
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>
#include <utils/qtcassert.h>
#include <QCoreApplication>
#include <cmath>
using namespace TextEditor;
using namespace Internal;
namespace {
static const QLatin1String kStay("#stay");
static const QLatin1String kPop("#pop");
static const QLatin1Char kBackSlash('\\');
static const QLatin1Char kHash('#');
static const QLatin1Char kExclamationMark('!');
}
class HighlighterCodeFormatterData : public CodeFormatterData
{
public:
HighlighterCodeFormatterData() :
m_foldingIndentDelta(0),
m_originalObservableState(-1),
m_continueObservableState(-1)
{}
~HighlighterCodeFormatterData() {}
int m_foldingIndentDelta;
int m_originalObservableState;
QStack<QString> m_foldingRegions;
int m_continueObservableState;
};
HighlighterCodeFormatterData *formatterData(const QTextBlock &block)
{
HighlighterCodeFormatterData *data = 0;
if (TextBlockUserData *userData = TextDocumentLayout::userData(block)) {
data = static_cast<HighlighterCodeFormatterData *>(userData->codeFormatterData());
if (!data) {
data = new HighlighterCodeFormatterData;
userData->setCodeFormatterData(data);
}
}
return data;
}
static TextStyle styleForFormat(int format)
{
const auto f = Highlighter::TextFormatId(format);
switch (f) {
case Highlighter::Normal: return C_TEXT;
case Highlighter::Keyword: return C_KEYWORD;
case Highlighter::DataType: return C_TYPE;
case Highlighter::Comment: return C_COMMENT;
case Highlighter::Decimal: return C_NUMBER;
case Highlighter::BaseN: return C_NUMBER;
case Highlighter::Float: return C_NUMBER;
case Highlighter::Char: return C_STRING;
case Highlighter::SpecialChar: return C_STRING;
case Highlighter::String: return C_STRING;
case Highlighter::Alert: return C_WARNING;
case Highlighter::Information: return C_TEXT;
case Highlighter::Warning: return C_WARNING;
case Highlighter::Error: return C_ERROR;
case Highlighter::Function: return C_FUNCTION;
case Highlighter::RegionMarker: return C_TEXT;
case Highlighter::BuiltIn: return C_PREPROCESSOR;
case Highlighter::Extension: return C_PRIMITIVE_TYPE;
case Highlighter::Operator: return C_OPERATOR;
case Highlighter::Variable: return C_LOCAL;
case Highlighter::Attribute: return C_LABEL;
case Highlighter::Annotation: return C_TEXT;
case Highlighter::CommentVar: return C_COMMENT;
case Highlighter::Import: return C_PREPROCESSOR;
case Highlighter::Others: return C_TEXT;
case Highlighter::Identifier: return C_LOCAL;
case Highlighter::Documentation: return C_DOXYGEN_COMMENT;
case Highlighter::ControlFlow: return C_KEYWORD;
case Highlighter::Preprocessor: return C_PREPROCESSOR;
case Highlighter::VerbatimString: return C_STRING;
case Highlighter::SpecialString: return C_STRING;
case Highlighter::Constant: return C_KEYWORD;
case Highlighter::TextFormatIdCount:
QTC_CHECK(false); // should never get here
return C_TEXT;
}
QTC_CHECK(false); // should never get here
return C_TEXT;
}
Highlighter::Highlighter(QTextDocument *parent) :
SyntaxHighlighter(parent),
m_regionDepth(0),
m_indentationBasedFolding(false),
m_tabSettings(0),
m_persistentObservableStatesCounter(PersistentsStart),
m_dynamicContextsCounter(0),
m_isBroken(false)
{
setTextFormatCategories(TextFormatIdCount, styleForFormat);
}
Highlighter::~Highlighter()
{}
// Mapping from Kate format strings to format ids.
struct KateFormatMap
{
KateFormatMap();
QHash<QString, Highlighter::TextFormatId> m_ids;
};
KateFormatMap::KateFormatMap()
{
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("dsComment"), Highlighter::Comment);
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("dsSpecialChar"), Highlighter::SpecialChar);
m_ids.insert(QLatin1String("dsString"), Highlighter::String);
m_ids.insert(QLatin1String("dsAlert"), Highlighter::Alert);
m_ids.insert(QLatin1String("dsInformation"), Highlighter::Information);
m_ids.insert(QLatin1String("dsWarning"), Highlighter::Warning);
m_ids.insert(QLatin1String("dsError"), Highlighter::Error);
m_ids.insert(QLatin1String("dsFunction"), Highlighter::Function);
m_ids.insert(QLatin1String("dsRegionMarker"), Highlighter::RegionMarker);
m_ids.insert(QLatin1String("dsBuiltIn"), Highlighter::BuiltIn);
m_ids.insert(QLatin1String("dsExtension"), Highlighter::Extension);
m_ids.insert(QLatin1String("dsOperator"), Highlighter::Operator);
m_ids.insert(QLatin1String("dsVariable"), Highlighter::Variable);
m_ids.insert(QLatin1String("dsAttribute"), Highlighter::Attribute);
m_ids.insert(QLatin1String("dsAnnotation"), Highlighter::Annotation);
m_ids.insert(QLatin1String("dsCommentVar"), Highlighter::CommentVar);
m_ids.insert(QLatin1String("dsImport"), Highlighter::Import);
m_ids.insert(QLatin1String("dsOthers"), Highlighter::Others);
m_ids.insert(QLatin1String("dsIdentifier"), Highlighter::Identifier);
m_ids.insert(QLatin1String("dsDocumentation"), Highlighter::Documentation);
m_ids.insert(QLatin1String("dsControlFlow"), Highlighter::ControlFlow);
m_ids.insert(QLatin1String("dsPreprocessor"), Highlighter::Preprocessor);
m_ids.insert(QLatin1String("dsVerbatimString"), Highlighter::VerbatimString);
m_ids.insert(QLatin1String("dsSpecialString"), Highlighter::SpecialString);
m_ids.insert(QLatin1String("dsConstant"), Highlighter::Constant);
}
Q_GLOBAL_STATIC(KateFormatMap, kateFormatMap)
void Highlighter::setDefaultContext(const QSharedPointer<Context> &defaultContext)
{
m_defaultContext = defaultContext;
m_persistentObservableStates.insert(m_defaultContext->name(), Default);
m_indentationBasedFolding = defaultContext->definition()->isIndentationBasedFolding();
}
void Highlighter::setTabSettings(const TabSettings &ts)
{
m_tabSettings = &ts;
}
static bool isOpeningParenthesis(QChar c)
{
return c == QLatin1Char('{') || c == QLatin1Char('[') || c == QLatin1Char('(');
}
static bool isClosingParenthesis(QChar c)
{
return c == QLatin1Char('}') || c == QLatin1Char(']') || c == QLatin1Char(')');
}
void Highlighter::highlightBlock(const QString &text)
{
if (!m_defaultContext.isNull() && !m_isBroken) {
try {
setupDataForBlock(text);
handleContextChange(m_currentContext->lineBeginContext(),
m_currentContext->definition());
ProgressData *progress = new ProgressData;
const int length = text.length();
while (progress->offset() < length)
iterateThroughRules(text, length, progress, false, m_currentContext->rules());
if (extractObservableState(currentBlockState()) != WillContinue) {
handleContextChange(m_currentContext->lineEndContext(),
m_currentContext->definition(),
false);
}
if (length == 0) {
handleContextChange(m_currentContext->lineEmptyContext(),
m_currentContext->definition(),
false);
}
delete progress;
m_contexts.clear();
if (m_indentationBasedFolding) {
applyIndentationBasedFolding(text);
} else {
applyRegionBasedFolding();
// In the case region depth has changed since the last time the state was set.
setCurrentBlockState(computeState(extractObservableState(currentBlockState())));
}
Parentheses parentheses;
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);
} catch (const HighlighterException &e) {
Core::MessageManager::write(
QCoreApplication::translate("GenericHighlighter",
"Generic highlighter error: %1")
.arg(e.message()),
Core::MessageManager::WithFocus);
m_isBroken = true;
}
}
formatSpaces(text);
}
void Highlighter::setupDataForBlock(const QString &text)
{
if (extractObservableState(currentBlockState()) == WillContinue)
analyseConsistencyOfWillContinueBlock(text);
if (previousBlockState() == -1) {
m_regionDepth = 0;
setupDefault();
} 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();
formatterData(currentBlock())->m_foldingRegions =
formatterData(currentBlock().previous())->m_foldingRegions;
}
assignCurrentContext();
}
void Highlighter::setupDefault()
{
m_contexts.push_back(m_defaultContext);
setCurrentBlockState(computeState(Default));
}
void Highlighter::setupFromWillContinue()
{
HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
pushContextSequence(previousData->m_continueObservableState);
HighlighterCodeFormatterData *data = formatterData(currentBlock());
data->m_originalObservableState = previousData->m_originalObservableState;
if (currentBlockState() == -1 || extractObservableState(currentBlockState()) == Default)
setCurrentBlockState(computeState(Continued));
}
void Highlighter::setupFromContinued()
{
HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
Q_ASSERT(previousData->m_originalObservableState != WillContinue &&
previousData->m_originalObservableState != Continued);
if (previousData->m_originalObservableState == Default ||
previousData->m_originalObservableState == -1) {
m_contexts.push_back(m_defaultContext);
} else {
pushContextSequence(previousData->m_originalObservableState);
}
setCurrentBlockState(computeState(previousData->m_originalObservableState));
}
void Highlighter::setupFromPersistent()
{
pushContextSequence(extractObservableState(previousBlockState()));
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;
if (!m_indentationBasedFolding) {
if (!rule->beginRegion().isEmpty()) {
formatterData(currentBlock())->m_foldingRegions.push(rule->beginRegion());
++m_regionDepth;
if (progress->isOpeningBraceMatchAtFirstNonSpace())
++formatterData(currentBlock())->m_foldingIndentDelta;
}
if (!rule->endRegion().isEmpty()) {
QStack<QString> *currentRegions =
&formatterData(currentBlock())->m_foldingRegions;
if (!currentRegions->isEmpty() && rule->endRegion() == currentRegions->top()) {
currentRegions->pop();
--m_regionDepth;
if (progress->isClosingBraceMatchAtNonEnd())
--formatterData(currentBlock())->m_foldingIndentDelta;
}
}
progress->clearBracesMatches();
}
const QString itemData = rule->itemData();
const QSharedPointer<HighlightDefinition> definition = rule->definition();
const bool lookAhead = rule->isLookAhead();
if (progress->isWillContinueLine()) {
createWillContinueBlock();
progress->setWillContinueLine(false);
} else {
if (rule->hasChildren())
iterateThroughRules(text, length, progress, true, rule->children());
if (!rule->context().isEmpty() && contextChangeRequired(rule->context())) {
m_currentCaptures = progress->captures();
changeContext(rule->context(), definition);
contextChanged = true;
}
}
// Do NOT access rule frome here on, because a context change might delete rule
// Format is not applied to child rules directly (but relative to the offset of their
// parent) nor to look ahead rules.
if (!childRule && !lookAhead) {
if (itemData.isEmpty())
applyFormat(startOffset, progress->offset() - startOffset,
m_currentContext->itemData(), m_currentContext->definition());
else
applyFormat(startOffset, progress->offset() - startOffset, itemData,
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());
if (progress->isOnlySpacesSoFar() && !text.at(progress->offset()).isSpace())
progress->setOnlySpacesSoFar(false);
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,
const bool assignCurrent)
{
QString identifier = contextName;
if (identifier.startsWith(kPop)) {
const QStringList complexOrder = contextName.split(kExclamationMark);
const QString orders = complexOrder.first();
identifier = complexOrder.size() > 1 ? complexOrder[1] : QString();
const int count = orders.splitRef(kHash, QString::SkipEmptyParts).size();
for (int i = 0; i < count; ++i) {
if (m_contexts.isEmpty()) {
throw HighlighterException(
QCoreApplication::translate("GenericHighlighter", "Reached empty context."));
}
m_contexts.pop_back();
}
if (extractObservableState(currentBlockState()) >= PersistentsStart) {
// One or more contexts were popped during during a persistent state.
const QString &currentSequence = currentContextSequence();
if (m_persistentObservableStates.contains(currentSequence))
setCurrentBlockState(
computeState(m_persistentObservableStates.value(currentSequence)));
else
setCurrentBlockState(
computeState(m_leadingObservableStates.value(currentSequence)));
}
}
if (!identifier.isEmpty()) {
const QSharedPointer<Context> &context = definition->context(identifier);
if (context->isDynamic())
pushDynamicContext(context);
else
m_contexts.push_back(context);
if (m_contexts.back()->lineEndContext() == kStay ||
extractObservableState(currentBlockState()) >= PersistentsStart) {
const QString &currentSequence = currentContextSequence();
mapLeadingSequence(currentSequence);
if (m_contexts.back()->lineEndContext() == kStay) {
// A persistent context was pushed.
mapPersistentSequence(currentSequence);
setCurrentBlockState(
computeState(m_persistentObservableStates.value(currentSequence)));
}
}
}
if (assignCurrent)
assignCurrentContext();
}
void Highlighter::handleContextChange(const QString &contextName,
const QSharedPointer<HighlightDefinition> &definition,
const bool setCurrent)
{
if (!contextName.isEmpty() && contextChangeRequired(contextName))
changeContext(contextName, definition, setCurrent);
}
static double luminance(const QColor &color)
{
// calculate the luminance based on
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
auto val = [](const double &colorVal) {
return colorVal < 0.03928 ? colorVal / 12.92 : std::pow((colorVal + 0.055) / 1.055, 2.4);
};
static QHash<QRgb, double> cache;
QHash<QRgb, double>::iterator it = cache.find(color.rgb());
if (it == cache.end()) {
it = cache.insert(color.rgb(), 0.2126 * val(color.redF())
+ 0.7152 * val(color.greenF())
+ 0.0722 * val(color.blueF()));
}
return it.value();
}
static float contrastRatio(const QColor &color1, const QColor &color2)
{
// calculate the contrast ratio based on
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
auto contrast = (luminance(color1) + 0.05) / (luminance(color2) + 0.05);
if (contrast < 1)
return 1 / contrast;
return contrast;
}
static bool isReadableOn(const QColor &background, const QColor &foreground)
{
// following the W3C Recommendation on contrast for large Text
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
return contrastRatio(background, foreground) > 3;
}
void Highlighter::applyFormat(int offset,
int count,
const QString &itemDataName,
const QSharedPointer<HighlightDefinition> &definition)
{
if (count == 0)
return;
QSharedPointer<ItemData> itemData;
try {
itemData = definition->itemData(itemDataName);
} catch (const HighlighterException &) {
// 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.
return;
}
TextFormatId formatId = kateFormatMap()->m_ids.value(itemData->style(), Normal);
if (formatId != Normal) {
QTextCharFormat format = formatForCategory(formatId);
if (itemData->isCustomized()) {
// Please notice that the following are applied every time for item data which have
// customizations. The configureFormats function could be used to provide a "one time"
// 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...
QBrush bg = format.background();
if (bg.style() == Qt::NoBrush)
bg = fontSettings().toTextCharFormat(C_TEXT).background();
if (itemData->color().isValid() && isReadableOn(bg.color(), itemData->color()))
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());
}
setFormat(offset, count, format);
}
}
void Highlighter::createWillContinueBlock()
{
HighlighterCodeFormatterData *data = formatterData(currentBlock());
const int currentObservableState = extractObservableState(currentBlockState());
if (currentObservableState == Continued) {
HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
data->m_originalObservableState = previousData->m_originalObservableState;
} else if (currentObservableState != WillContinue) {
data->m_originalObservableState = currentObservableState;
}
const QString currentSequence = currentContextSequence();
mapPersistentSequence(currentSequence);
data->m_continueObservableState = m_persistentObservableStates.value(currentSequence);
m_persistentContexts.insert(data->m_continueObservableState, m_contexts);
setCurrentBlockState(computeState(WillContinue));
}
void Highlighter::analyseConsistencyOfWillContinueBlock(const QString &text)
{
if (currentBlock().next().isValid() && (
text.length() == 0 || text.at(text.length() - 1) != kBackSlash) &&
extractObservableState(currentBlock().next().userState()) != Continued) {
currentBlock().next().setUserState(computeState(Continued));
}
if (text.length() == 0 || text.at(text.length() - 1) != kBackSlash) {
HighlighterCodeFormatterData *data = formatterData(currentBlock());
setCurrentBlockState(computeState(data->m_originalObservableState));
}
}
void Highlighter::mapPersistentSequence(const QString &contextSequence)
{
if (!m_persistentObservableStates.contains(contextSequence)) {
int newState = m_persistentObservableStatesCounter;
m_persistentObservableStates.insert(contextSequence, newState);
m_persistentContexts.insert(newState, m_contexts);
++m_persistentObservableStatesCounter;
}
}
void Highlighter::mapLeadingSequence(const QString &contextSequence)
{
if (!m_leadingObservableStates.contains(contextSequence))
m_leadingObservableStates.insert(contextSequence,
extractObservableState(currentBlockState()));
}
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;
}
void Highlighter::assignCurrentContext()
{
if (m_contexts.isEmpty()) {
// 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.
m_contexts.push_back(m_defaultContext);
}
m_currentContext = m_contexts.back();
}
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;
}
void Highlighter::applyRegionBasedFolding() const
{
int folding = 0;
TextBlockUserData *currentBlockUserData = TextDocumentLayout::userData(currentBlock());
HighlighterCodeFormatterData *data = formatterData(currentBlock());
HighlighterCodeFormatterData *previousData = formatterData(currentBlock().previous());
if (previousData) {
folding = extractRegionDepth(previousBlockState());
if (data->m_foldingIndentDelta != 0) {
folding += data->m_foldingIndentDelta;
if (data->m_foldingIndentDelta > 0)
currentBlockUserData->setFoldingStartIncluded(true);
else
TextDocumentLayout::userData(currentBlock().previous())->setFoldingEndIncluded(false);
data->m_foldingIndentDelta = 0;
}
}
currentBlockUserData->setFoldingEndIncluded(true);
currentBlockUserData->setFoldingIndent(folding);
}
void Highlighter::applyIndentationBasedFolding(const QString &text) const
{
TextBlockUserData *data = TextDocumentLayout::userData(currentBlock());
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());
}
}
}