From 15faf23e07dc206215841cb87e10a95d89ee0c25 Mon Sep 17 00:00:00 2001
From: Leandro Melo <leandro.melo@nokia.com>
Date: Mon, 21 Jun 2010 13:37:53 +0200
Subject: [PATCH] Generic highlighter: Indentation based code folding.

---
 .../highlightdefinition.cpp                   |  9 +-
 .../generichighlighter/highlightdefinition.h  |  5 ++
 .../highlightdefinitionhandler.cpp            |  9 ++
 .../highlightdefinitionhandler.h              |  1 +
 .../generichighlighter/highlighter.cpp        | 90 ++++++++++++++-----
 .../generichighlighter/highlighter.h          | 12 ++-
 src/plugins/texteditor/plaintexteditor.cpp    | 11 +++
 src/plugins/texteditor/plaintexteditor.h      |  1 +
 .../basetextdocumentlayout.h                  |  8 +-
 .../highlighterengine/highlighterengine.pro   |  3 +-
 .../highlighterengine/tabsettings.h           | 46 ++++++++++
 11 files changed, 164 insertions(+), 31 deletions(-)
 create mode 100644 tests/auto/generichighlighter/highlighterengine/tabsettings.h

diff --git a/src/plugins/texteditor/generichighlighter/highlightdefinition.cpp b/src/plugins/texteditor/generichighlighter/highlightdefinition.cpp
index 4d6d92c8974..c06b49d1e0f 100644
--- a/src/plugins/texteditor/generichighlighter/highlightdefinition.cpp
+++ b/src/plugins/texteditor/generichighlighter/highlightdefinition.cpp
@@ -42,7 +42,8 @@ using namespace Internal;
 HighlightDefinition::HighlightDefinition() :
     m_delimiters(QLatin1String(".():!+,-<=>%&/;?[]^{|}~\\*, \t")),
     m_singleLineCommentAfterWhiteSpaces(false),
-    m_keywordCaseSensitivity(Qt::CaseSensitive)
+    m_keywordCaseSensitivity(Qt::CaseSensitive),
+    m_indentationBasedFolding(false)
 {}
 
 HighlightDefinition::~HighlightDefinition()
@@ -165,3 +166,9 @@ void HighlightDefinition::setKeywordsSensitive(const QString &sensitivity)
 
 Qt::CaseSensitivity HighlightDefinition::keywordsSensitive() const
 { return m_keywordCaseSensitivity; }
+
+void HighlightDefinition::setIndentationBasedFolding(const QString &indentationBasedFolding)
+{ m_indentationBasedFolding = toBool(indentationBasedFolding); }
+
+bool HighlightDefinition::isIndentationBasedFolding() const
+{ return m_indentationBasedFolding; }
diff --git a/src/plugins/texteditor/generichighlighter/highlightdefinition.h b/src/plugins/texteditor/generichighlighter/highlightdefinition.h
index dd94838d870..ca7afd93761 100644
--- a/src/plugins/texteditor/generichighlighter/highlightdefinition.h
+++ b/src/plugins/texteditor/generichighlighter/highlightdefinition.h
@@ -80,6 +80,9 @@ public:
     void setMultiLineCommentRegion(const QString &region);
     const QString &multiLineCommentRegion() const;
 
+    void setIndentationBasedFolding(const QString &indentationBasedFolding);
+    bool isIndentationBasedFolding() const;
+
 private:
     Q_DISABLE_COPY(HighlightDefinition)
 
@@ -109,6 +112,8 @@ private:
     QString m_multiLineCommentRegion;
 
     Qt::CaseSensitivity m_keywordCaseSensitivity;
+
+    bool m_indentationBasedFolding;
 };
 
 } // namespace Internal
diff --git a/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.cpp b/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.cpp
index a157d944611..58a7b4bcd35 100644
--- a/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.cpp
+++ b/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.cpp
@@ -103,6 +103,8 @@ namespace {
     static const QLatin1String kLanguage("language");
     static const QLatin1String kExtensions("extensions");
     static const QLatin1String kIncludeAttrib("includeAttrib");
+    static const QLatin1String kFolding("folding");
+    static const QLatin1String kIndentationSensitive("indentationsensitive");
     static const QLatin1String kHash("#");
     static const QLatin1String kDoubleHash("##");
 }
@@ -145,6 +147,8 @@ bool HighlightDefinitionHandler::startElement(const QString &,
         commentElementStarted(atts);
     } else if (qName == kKeywords) {
         keywordsElementStarted(atts);
+    } else if (qName == kFolding) {
+        foldingElementStarted(atts);
     } else if (qName == kDetectChar) {
         detectCharStarted(atts);
     } else if (qName == kDetect2Chars) {
@@ -290,6 +294,11 @@ void HighlightDefinitionHandler::keywordsElementStarted(const QXmlAttributes &at
     //@todo: wordWrapDelimiters?
 }
 
+void HighlightDefinitionHandler::foldingElementStarted(const QXmlAttributes &atts) const
+{
+    m_definition->setIndentationBasedFolding(atts.value(kIndentationSensitive));
+}
+
 void HighlightDefinitionHandler::detectCharStarted(const QXmlAttributes &atts)
 {
     DetectCharRule *rule = new DetectCharRule;
diff --git a/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.h b/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.h
index e08be97649f..672a3684886 100644
--- a/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.h
+++ b/src/plugins/texteditor/generichighlighter/highlightdefinitionhandler.h
@@ -65,6 +65,7 @@ private:
     void itemDataElementStarted(const QXmlAttributes &atts) const;
     void commentElementStarted(const QXmlAttributes &atts) const;
     void keywordsElementStarted(const QXmlAttributes &atts) const;
+    void foldingElementStarted(const QXmlAttributes &atts) const;
     void ruleElementStarted(const QXmlAttributes &atts, const QSharedPointer<Rule> &rule);
 
     // Specific rules.
diff --git a/src/plugins/texteditor/generichighlighter/highlighter.cpp b/src/plugins/texteditor/generichighlighter/highlighter.cpp
index aba84b8c4f8..9ec1046431c 100644
--- a/src/plugins/texteditor/generichighlighter/highlighter.cpp
+++ b/src/plugins/texteditor/generichighlighter/highlighter.cpp
@@ -35,6 +35,7 @@
 #include "highlighterexception.h"
 #include "progressdata.h"
 #include "reuse.h"
+#include "tabsettings.h"
 
 #include <QtCore/QLatin1String>
 #include <QtCore/QLatin1Char>
@@ -54,6 +55,8 @@ const Highlighter::KateFormatMap Highlighter::m_kateFormats;
 Highlighter::Highlighter(QTextDocument *parent) :
     QSyntaxHighlighter(parent),
     m_regionDepth(0),
+    m_indentationBasedFolding(false),
+    m_tabSettings(0),
     m_persistentObservableStatesCounter(PersistentsStart),
     m_dynamicContextsCounter(0),
     m_isBroken(false)
@@ -95,6 +98,12 @@ void  Highlighter::setDefaultContext(const QSharedPointer<Context> &defaultConte
 {
     m_defaultContext = defaultContext;
     m_persistentObservableStates.insert(m_defaultContext->name(), Default);
+    m_indentationBasedFolding = defaultContext->definition()->isIndentationBasedFolding();
+}
+
+void Highlighter::setTabSettings(const TabSettings &ts)
+{
+    m_tabSettings = &ts;
 }
 
 void Highlighter::highlightBlock(const QString &text)
@@ -118,11 +127,14 @@ void Highlighter::highlightBlock(const QString &text)
                                 false);
             m_contexts.clear();
 
-            applyFolding();
+            if (m_indentationBasedFolding) {
+                applyIndentationBasedFolding(text);
+            } else {
+                applyRegionBasedFolding();
 
-            // Takes into the account any change that might have affected the region depth since
-            // the last time the state was set.
-            setCurrentBlockState(computeState(extractObservableState(currentBlockState())));
+                // In the case region depth has changed since the last time the state was set.
+                setCurrentBlockState(computeState(extractObservableState(currentBlockState())));
+            }
         } catch (const HighlighterException &) {
             m_isBroken = true;
         }
@@ -220,24 +232,25 @@ void Highlighter::iterateThroughRules(const QString &text,
         if (rule->matchSucceed(text, length, progress)) {
             atLeastOneMatch = true;
 
-            // Code folding.
-            if (!rule->beginRegion().isEmpty()) {
-                blockData(currentBlockUserData())->m_foldingRegions.push(rule->beginRegion());
-                ++m_regionDepth;
-                if (progress->isOpeningBraceMatchAtFirstNonSpace())
-                    ++blockData(currentBlockUserData())->m_foldingIndentDelta;
-            }
-            if (!rule->endRegion().isEmpty()) {
-                QStack<QString> *currentRegions =
-                    &blockData(currentBlockUserData())->m_foldingRegions;
-                if (!currentRegions->isEmpty() && rule->endRegion() == currentRegions->top()) {
-                    currentRegions->pop();
-                    --m_regionDepth;
-                    if (progress->isClosingBraceMatchAtNonEnd())
-                        --blockData(currentBlockUserData())->m_foldingIndentDelta;
+            if (!m_indentationBasedFolding) {
+                if (!rule->beginRegion().isEmpty()) {
+                    blockData(currentBlockUserData())->m_foldingRegions.push(rule->beginRegion());
+                    ++m_regionDepth;
+                    if (progress->isOpeningBraceMatchAtFirstNonSpace())
+                        ++blockData(currentBlockUserData())->m_foldingIndentDelta;
                 }
+                if (!rule->endRegion().isEmpty()) {
+                    QStack<QString> *currentRegions =
+                        &blockData(currentBlockUserData())->m_foldingRegions;
+                    if (!currentRegions->isEmpty() && rule->endRegion() == currentRegions->top()) {
+                        currentRegions->pop();
+                        --m_regionDepth;
+                        if (progress->isClosingBraceMatchAtNonEnd())
+                            --blockData(currentBlockUserData())->m_foldingIndentDelta;
+                    }
+                }
+                progress->clearBracesMatches();
             }
-            progress->clearBracesMatches();
 
             if (progress->isWillContinueLine()) {
                 createWillContinueBlock();
@@ -524,7 +537,7 @@ int Highlighter::computeState(const int observableState) const
     return m_regionDepth << 12 | observableState;
 }
 
-void Highlighter::applyFolding() const
+void Highlighter::applyRegionBasedFolding() const
 {
     int folding = 0;
     BlockData *data = blockData(currentBlockUserData());
@@ -543,3 +556,38 @@ void Highlighter::applyFolding() const
     data->setFoldingEndIncluded(true);
     data->setFoldingIndent(folding);
 }
+
+void Highlighter::applyIndentationBasedFolding(const QString &text) const
+{
+    BlockData *data = blockData(currentBlockUserData());
+    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());
+        }
+    }
+}
diff --git a/src/plugins/texteditor/generichighlighter/highlighter.h b/src/plugins/texteditor/generichighlighter/highlighter.h
index 7ca7e9d649d..ee3b94019cb 100644
--- a/src/plugins/texteditor/generichighlighter/highlighter.h
+++ b/src/plugins/texteditor/generichighlighter/highlighter.h
@@ -42,6 +42,9 @@
 #include <QtGui/QTextCharFormat>
 
 namespace TextEditor {
+
+struct TabSettings;
+
 namespace Internal {
 
 class Rule;
@@ -72,8 +75,9 @@ public:
         RegionMarker,
         Others
     };
-    void configureFormat(TextFormatId id, const QTextCharFormat &format);
 
+    void configureFormat(TextFormatId id, const QTextCharFormat &format);
+    void setTabSettings(const TabSettings &ts);
     void setDefaultContext(const QSharedPointer<Context> &defaultContext);
 
 protected:
@@ -118,7 +122,9 @@ private:
                      const QSharedPointer<HighlightDefinition> &definition);
     void applyVisualWhitespaceFormat(const QString &text);
 
-    void applyFolding() const;
+    void applyRegionBasedFolding() const;
+    void applyIndentationBasedFolding(const QString &text) const;
+    int neighbouringNonEmptyBlockIndent(QTextBlock block, const bool previous) const;
 
     // Mapping from Kate format strings to format ids.
     struct KateFormatMap
@@ -166,6 +172,8 @@ private:
     static int extractObservableState(const int state);
 
     int m_regionDepth;
+    bool m_indentationBasedFolding;
+    const TabSettings *m_tabSettings;
 
     int m_persistentObservableStatesCounter;
     int m_dynamicContextsCounter;
diff --git a/src/plugins/texteditor/plaintexteditor.cpp b/src/plugins/texteditor/plaintexteditor.cpp
index d945b166711..ce933292c04 100644
--- a/src/plugins/texteditor/plaintexteditor.cpp
+++ b/src/plugins/texteditor/plaintexteditor.cpp
@@ -153,6 +153,17 @@ void PlainTextEditor::setFontSettings(const FontSettings &fs)
     }
 }
 
+void PlainTextEditor::setTabSettings(const TextEditor::TabSettings &ts)
+{
+    BaseTextEditor::setTabSettings(ts);
+
+    if (baseTextDocument()->syntaxHighlighter()) {
+        Highlighter *highlighter =
+            static_cast<Highlighter *>(baseTextDocument()->syntaxHighlighter());
+        highlighter->setTabSettings(ts);
+    }
+}
+
 void PlainTextEditor::fileChanged()
 {
     configure(Core::ICore::instance()->mimeDatabase()->findByFile(file()->fileName()));
diff --git a/src/plugins/texteditor/plaintexteditor.h b/src/plugins/texteditor/plaintexteditor.h
index d027ef07ff4..c57aba920c7 100644
--- a/src/plugins/texteditor/plaintexteditor.h
+++ b/src/plugins/texteditor/plaintexteditor.h
@@ -76,6 +76,7 @@ public:
 public slots:
     virtual void unCommentSelection();
     virtual void setFontSettings(const FontSettings &fs);
+    virtual void setTabSettings(const TextEditor::TabSettings &);
 
 private slots:
     void fileChanged();
diff --git a/tests/auto/generichighlighter/highlighterengine/basetextdocumentlayout.h b/tests/auto/generichighlighter/highlighterengine/basetextdocumentlayout.h
index f3433db6327..8687354a54e 100644
--- a/tests/auto/generichighlighter/highlighterengine/basetextdocumentlayout.h
+++ b/tests/auto/generichighlighter/highlighterengine/basetextdocumentlayout.h
@@ -30,14 +30,10 @@
 #ifndef BASETEXTDOCUMENTLAYOUT_H
 #define BASETEXTDOCUMENTLAYOUT_H
 
-/*
- Since the text editor plugin directory is not included in the search list of the pro file, this
- file replaces the "real" basetextdocumentlayout.h file. The objective is to provide a simple
- TextBlockUserData and avoid "external" dependencies or intrusive defines.
- */
-
 #include <QtGui/QTextBlockUserData>
 
+// Replaces the "real" basetextdocumentlayout.h file.
+
 struct TextBlockUserData : QTextBlockUserData
 {
     virtual ~TextBlockUserData(){}
diff --git a/tests/auto/generichighlighter/highlighterengine/highlighterengine.pro b/tests/auto/generichighlighter/highlighterengine/highlighterengine.pro
index dec3fa3bd89..7e60b51ea1e 100644
--- a/tests/auto/generichighlighter/highlighterengine/highlighterengine.pro
+++ b/tests/auto/generichighlighter/highlighterengine/highlighterengine.pro
@@ -18,7 +18,8 @@ SOURCES += tst_highlighterengine.cpp \
 HEADERS += \
     highlightermock.h \
     basetextdocumentlayout.h \
-    formats.h
+    formats.h \
+    tabsettings.h
 
 INCLUDEPATH += $$GENERICHIGHLIGHTERDIR
 
diff --git a/tests/auto/generichighlighter/highlighterengine/tabsettings.h b/tests/auto/generichighlighter/highlighterengine/tabsettings.h
new file mode 100644
index 00000000000..b79bbdbaf81
--- /dev/null
+++ b/tests/auto/generichighlighter/highlighterengine/tabsettings.h
@@ -0,0 +1,46 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef TABSETTINGS_H
+#define TABSETTINGS_H
+
+#include <QString>
+
+// Replaces the "real" tabsettings.h file.
+
+namespace TextEditor {
+
+struct TabSettings
+{
+    int indentationColumn(const QString &) const { return 0; }
+};
+
+}
+
+#endif // TABSETTINGS_H
-- 
GitLab