From ee47b652a61e153ee82732b391a94e45d012ad7b Mon Sep 17 00:00:00 2001
From: Tobias Hunger <tobias.hunger@digia.com>
Date: Fri, 9 Aug 2013 17:45:14 +0200
Subject: [PATCH] Snippets: Allow lowercase/titlecase/uppercase modifiers for
 variables

Use the same syntax already used in the custom wizard to denote
variables that are modified to be lower-/title-/uppercase:

 $tESt:u$ will become TEST
 $tESt:c$ will become TESt
 $tESt:l$ will become test

The snippet will be inserted without any name mangling happening.
Once the editing is done the name mangling is applied to all fields.

Change-Id: I7c1f5a1ad2bb5acf1b88b54de51bb39391c64763
Reviewed-by: David Schulz <david.schulz@digia.com>
---
 share/qtcreator/snippets/cpp.xml             |   2 +-
 src/plugins/texteditor/basetexteditor.cpp    |  11 ++
 src/plugins/texteditor/snippets/snippet.cpp  | 148 ++++++++++++++++---
 src/plugins/texteditor/snippets/snippet.h    |  14 +-
 src/plugins/texteditor/texteditoroverlay.cpp |  28 +++-
 src/plugins/texteditor/texteditoroverlay.h   |   4 +
 6 files changed, 184 insertions(+), 23 deletions(-)

diff --git a/share/qtcreator/snippets/cpp.xml b/share/qtcreator/snippets/cpp.xml
index 549b1bd6c80..2eadb38e3e5 100644
--- a/share/qtcreator/snippets/cpp.xml
+++ b/share/qtcreator/snippets/cpp.xml
@@ -71,5 +71,5 @@ case $value$:
 default:
     break;
 }</snippet>
-<snippet group="C++" trigger="Q_PROPERTY" id="cpp_q_property">Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name$ NOTIFY $name$Changed)</snippet>
+<snippet group="C++" trigger="Q_PROPERTY" id="cpp_q_property">Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed)</snippet>
 </snippets>
diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp
index 50a728de9a6..4b34ea0132d 100644
--- a/src/plugins/texteditor/basetexteditor.cpp
+++ b/src/plugins/texteditor/basetexteditor.cpp
@@ -1559,6 +1559,7 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
                 && d->m_snippetOverlay->isVisible()) {
             e->accept();
             d->m_snippetOverlay->hide();
+            d->m_snippetOverlay->mangle();
             d->m_snippetOverlay->clear();
             QTextCursor cursor = textCursor();
             cursor.clearSelection();
@@ -1598,6 +1599,7 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
         if (d->m_snippetOverlay->isVisible()) {
             e->accept();
             d->m_snippetOverlay->hide();
+            d->m_snippetOverlay->mangle();
             d->m_snippetOverlay->clear();
             QTextCursor cursor = textCursor();
             cursor.movePosition(QTextCursor::EndOfBlock);
@@ -1942,6 +1944,7 @@ void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, cons
     cursor.insertText(data.text);
     QList<QTextEdit::ExtraSelection> selections;
 
+    QList<NameMangler *> manglers;
     for (int i = 0; i < data.ranges.count(); ++i) {
         int position = data.ranges.at(i).start + startCursorPosition;
         int length = data.ranges.at(i).length;
@@ -1953,6 +1956,7 @@ void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, cons
         selection.cursor = tc;
         selection.format = (length ? d->m_occurrencesFormat : d->m_occurrenceRenameFormat);
         selections.append(selection);
+        manglers << data.ranges.at(i).mangler;
     }
 
     cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
@@ -1960,6 +1964,7 @@ void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, cons
     cursor.endEditBlock();
 
     setExtraSelections(BaseTextEditorWidget::SnippetPlaceholderSelection, selections);
+    d->m_snippetOverlay->setNameMangler(manglers);
 
     if (!selections.isEmpty()) {
         const QTextEdit::ExtraSelection &selection = selections.first();
@@ -2516,6 +2521,7 @@ bool BaseTextEditorWidgetPrivate::snippetCheckCursor(const QTextCursor &cursor)
         || !m_snippetOverlay->hasCursorInSelection(end)
         || m_snippetOverlay->hasFirstSelectionBeginMoved()) {
         m_snippetOverlay->setVisible(false);
+        m_snippetOverlay->mangle();
         m_snippetOverlay->clear();
         return false;
     }
@@ -4696,6 +4702,7 @@ void BaseTextEditorWidget::handleBackspaceKey()
             handled = true;
         } else {
             if (cursorWithinSnippet) {
+                d->m_snippetOverlay->mangle();
                 d->m_snippetOverlay->clear();
                 cursorWithinSnippet = false;
             }
@@ -4728,6 +4735,7 @@ void BaseTextEditorWidget::handleBackspaceKey()
             cursor.deletePreviousChar();
         } else {
             if (cursorWithinSnippet) {
+                d->m_snippetOverlay->mangle();
                 d->m_snippetOverlay->clear();
                 cursorWithinSnippet = false;
             }
@@ -5317,6 +5325,7 @@ void BaseTextEditorWidget::setExtraSelections(ExtraSelectionKind kind, const QLi
         }
         d->m_overlay->setVisible(!d->m_overlay->isEmpty());
     } else if (kind == SnippetPlaceholderSelection) {
+        d->m_snippetOverlay->mangle();
         d->m_snippetOverlay->clear();
         foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
             d->m_snippetOverlay->addOverlaySelection(selection.cursor,
@@ -5990,6 +5999,7 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
 
         if (d->m_snippetOverlay->isVisible() && lines.count() > 1) {
             d->m_snippetOverlay->hide();
+            d->m_snippetOverlay->mangle();
             d->m_snippetOverlay->clear();
         }
 
@@ -6006,6 +6016,7 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
     if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n'))
                                              || text.contains(QLatin1Char('\t')))) {
         d->m_snippetOverlay->hide();
+        d->m_snippetOverlay->mangle();
         d->m_snippetOverlay->clear();
     }
 
diff --git a/src/plugins/texteditor/snippets/snippet.cpp b/src/plugins/texteditor/snippets/snippet.cpp
index db681faf652..3ff6a36c4e8 100644
--- a/src/plugins/texteditor/snippets/snippet.cpp
+++ b/src/plugins/texteditor/snippets/snippet.cpp
@@ -29,12 +29,54 @@
 
 #include "snippet.h"
 
+#include <coreplugin/id.h>
+
 #include <QLatin1Char>
 #include <QLatin1String>
 #include <QTextDocument>
 
 using namespace TextEditor;
 
+const char NOMANGLER_ID[] = "TextEditor::NoMangler";
+const char UCMANGLER_ID[] = "TextEditor::UppercaseMangler";
+const char LCMANGLER_ID[] = "TextEditor::LowercaseMangler";
+const char TCMANGLER_ID[] = "TextEditor::TitlecaseMangler";
+
+// --------------------------------------------------------------------
+// Manglers:
+// --------------------------------------------------------------------
+
+class UppercaseMangler : public NameMangler
+{
+public:
+    Core::Id id() const { return UCMANGLER_ID; }
+    QString mangle(const QString &unmangled) const { return unmangled.toUpper(); }
+};
+
+class LowercaseMangler : public NameMangler
+{
+public:
+    Core::Id id() const { return LCMANGLER_ID; }
+    QString mangle(const QString &unmangled) const { return unmangled.toLower(); }
+};
+
+class TitlecaseMangler : public NameMangler
+{
+public:
+    Core::Id id() const { return TCMANGLER_ID; }
+    QString mangle(const QString &unmangled) const
+    {
+        QString result = unmangled;
+        if (!result.isEmpty())
+            result[0] = unmangled.at(0).toTitleCase();
+        return result;
+    }
+};
+
+// --------------------------------------------------------------------
+// Snippet:
+// --------------------------------------------------------------------
+
 const QChar Snippet::kVariableDelimiter(QLatin1Char('$'));
 
 Snippet::Snippet(const QString &groupId, const QString &id) :
@@ -145,12 +187,17 @@ QString Snippet::generateTip() const
 
 Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
 {
+    static UppercaseMangler ucMangler;
+    static LowercaseMangler lcMangler;
+    static TitlecaseMangler tcMangler;
+
     Snippet::ParsedSnippet result;
     result.success = true;
 
     const int count = snippet.count();
     bool success = true;
     int start = -1;
+    NameMangler *mangler = 0;
 
     result.text.reserve(count);
 
@@ -158,6 +205,45 @@ Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
         QChar current = snippet.at(i);
         QChar next = (i + 1) < count ? snippet.at(i + 1) : QChar();
 
+        if (current == Snippet::kVariableDelimiter) {
+            if (start < 0) {
+                // start delimiter:
+                start = result.text.count();
+            } else {
+                int length = result.text.count() - start;
+                result.ranges << ParsedSnippet::Range(start, length, mangler);
+                mangler = 0;
+                start = -1;
+            }
+            continue;
+        }
+
+        if (mangler) {
+            success = false;
+            break;
+        }
+
+        if (current == QLatin1Char(':')) {
+            if (start >= 0) {
+                if (mangler != 0) {
+                    success = false;
+                    break;
+                }
+                if (next == QLatin1Char('l')) {
+                    mangler = &lcMangler;
+                } else if (next == QLatin1Char('u')) {
+                    mangler = &ucMangler;
+                } else if (next == QLatin1Char('c')) {
+                    mangler = &tcMangler;
+                } else {
+                    success = false;
+                    break;
+                }
+            }
+            ++i;
+            continue;
+        }
+
         if (current == QLatin1Char('\\')) {
             if (next.isNull()) {
                 success = false;
@@ -168,16 +254,6 @@ Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
             continue;
         }
 
-        if (current == Snippet::kVariableDelimiter) {
-            if (start < 0) {
-                // start delimiter:
-                start = result.text.count();
-            } else {
-                result.ranges << ParsedSnippet::Range(start, result.text.count() - start);
-                start = -1;
-            }
-            continue;
-        }
         result.text.append(current);
     }
 
@@ -206,34 +282,60 @@ void Internal::TextEditorPlugin::testSnippetParsing_data()
     QTest::addColumn<bool>("success");
     QTest::addColumn<QList<int> >("ranges_start");
     QTest::addColumn<QList<int> >("ranges_length");
+    QTest::addColumn<QList<Core::Id> >("ranges_mangler");
 
     QTest::newRow("no input")
-            << QString() << QString() << true << (QList<int>()) << (QList<int>());
+            << QString() << QString() << true
+            << (QList<int>()) << (QList<int>()) << (QList<Core::Id>());
     QTest::newRow("empty input")
-            << QString::fromLatin1("") << QString::fromLatin1("") << true << (QList<int>()) << (QList<int>());
+            << QString::fromLatin1("") << QString::fromLatin1("") << true
+            << (QList<int>()) << (QList<int>()) << (QList<Core::Id>());
 
     QTest::newRow("simple identifier")
-            << QString::fromLatin1("$test$") << QString::fromLatin1("test") << true
-            << (QList<int>() << 0) << (QList<int>() << 4);
+            << QString::fromLatin1("$tESt$") << QString::fromLatin1("tESt") << true
+            << (QList<int>() << 0) << (QList<int>() << 4)
+            << (QList<Core::Id>() << NOMANGLER_ID);
+    QTest::newRow("simple identifier with lc")
+            << QString::fromLatin1("$tESt:l$") << QString::fromLatin1("tESt") << true
+            << (QList<int>() << 0) << (QList<int>() << 4)
+            << (QList<Core::Id>() << LCMANGLER_ID);
+    QTest::newRow("simple identifier with uc")
+            << QString::fromLatin1("$tESt:u$") << QString::fromLatin1("tESt") << true
+            << (QList<int>() << 0) << (QList<int>() << 4)
+            << (QList<Core::Id>() << UCMANGLER_ID);
+    QTest::newRow("simple identifier with tc")
+            << QString::fromLatin1("$tESt:c$") << QString::fromLatin1("tESt") << true
+            << (QList<int>() << 0) << (QList<int>() << 4)
+            << (QList<Core::Id>() << TCMANGLER_ID);
+
     QTest::newRow("escaped string")
             << QString::fromLatin1("\\$test\\$") << QString::fromLatin1("$test$") << true
-            << (QList<int>()) << (QList<int>());
+            << (QList<int>()) << (QList<int>())
+            << (QList<Core::Id>());
     QTest::newRow("escaped escape")
             << QString::fromLatin1("\\\\$test\\\\$") << QString::fromLatin1("\\test\\") << true
-            << (QList<int>() << 1) << (QList<int>() << 5);
+            << (QList<int>() << 1) << (QList<int>() << 5)
+            << (QList<Core::Id>() << NOMANGLER_ID);
 
     QTest::newRow("Q_PROPERTY")
-            << QString::fromLatin1("Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name$ NOTIFY $name$Changed)")
+            << QString::fromLatin1("Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed)")
             << QString::fromLatin1("Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged)") << true
             << (QList<int>() << 11 << 16 << 26 << 40 << 52)
-            << (QList<int>() << 4 << 4 << 4 << 4 << 4);
+            << (QList<int>() << 4 << 4 << 4 << 4 << 4)
+            << (QList<Core::Id>() << NOMANGLER_ID << NOMANGLER_ID << NOMANGLER_ID << TCMANGLER_ID << NOMANGLER_ID);
 
     QTest::newRow("broken escape")
             << QString::fromLatin1("\\\\$test\\\\$\\") << QString::fromLatin1("\\\\$test\\\\$\\") << false
-            << (QList<int>()) << (QList<int>());
+            << (QList<int>()) << (QList<int>())
+            << (QList<Core::Id>());
     QTest::newRow("open identifier")
             << QString::fromLatin1("$test") << QString::fromLatin1("$test") << false
-            << (QList<int>()) << (QList<int>());
+            << (QList<int>()) << (QList<int>())
+            << (QList<Core::Id>());
+    QTest::newRow("wrong mangler")
+            << QString::fromLatin1("$test:X$") << QString::fromLatin1("$test:X$") << false
+            << (QList<int>()) << (QList<int>())
+            << (QList<Core::Id>());
 }
 
 void Internal::TextEditorPlugin::testSnippetParsing()
@@ -243,7 +345,9 @@ void Internal::TextEditorPlugin::testSnippetParsing()
     QFETCH(bool, success);
     QFETCH(QList<int>, ranges_start);
     QFETCH(QList<int>, ranges_length);
+    QFETCH(QList<Core::Id>, ranges_mangler);
     Q_ASSERT(ranges_start.count() == ranges_length.count()); // sanity check for the test data
+    Q_ASSERT(ranges_start.count() == ranges_mangler.count()); // sanity check for the test data
 
     Snippet::ParsedSnippet result = Snippet::parse(input);
 
@@ -253,6 +357,10 @@ void Internal::TextEditorPlugin::testSnippetParsing()
     for (int i = 0; i < ranges_start.count(); ++i) {
         QCOMPARE(result.ranges.at(i).start, ranges_start.at(i));
         QCOMPARE(result.ranges.at(i).length, ranges_length.at(i));
+        Core::Id id = NOMANGLER_ID;
+        if (result.ranges.at(i).mangler)
+            id = result.ranges.at(i).mangler->id();
+        QCOMPARE(id, ranges_mangler.at(i));
     }
 }
 #endif
diff --git a/src/plugins/texteditor/snippets/snippet.h b/src/plugins/texteditor/snippets/snippet.h
index dcd5d240111..8163f31a47a 100644
--- a/src/plugins/texteditor/snippets/snippet.h
+++ b/src/plugins/texteditor/snippets/snippet.h
@@ -36,8 +36,19 @@
 #include <QList>
 #include <QString>
 
+namespace Core { class Id; }
+
 namespace TextEditor {
 
+class TEXTEDITOR_EXPORT NameMangler
+{
+public:
+    virtual ~NameMangler() { }
+
+    virtual Core::Id id() const = 0;
+    virtual QString mangle(const QString &unmangled) const = 0;
+};
+
 class TEXTEDITOR_EXPORT Snippet
 {
 public:
@@ -73,9 +84,10 @@ public:
         QString text;
         bool success;
         struct Range {
-            Range(int s, int l) : start(s), length(l) { }
+            Range(int s, int l, NameMangler *m) : start(s), length(l), mangler(m) { }
             int start;
             int length;
+            NameMangler *mangler;
         };
         QList<Range> ranges;
     };
diff --git a/src/plugins/texteditor/texteditoroverlay.cpp b/src/plugins/texteditor/texteditoroverlay.cpp
index a20376364aa..102e917c9c0 100644
--- a/src/plugins/texteditor/texteditoroverlay.cpp
+++ b/src/plugins/texteditor/texteditoroverlay.cpp
@@ -29,6 +29,7 @@
 
 #include "texteditoroverlay.h"
 #include "basetexteditor.h"
+#include "snippets/snippet.h"
 
 #include <QDebug>
 #include <QMap>
@@ -72,6 +73,8 @@ void TextEditorOverlay::clear()
         return;
     m_selections.clear();
     m_firstSelectionOriginalBegin = -1;
+    m_equivalentSelections.clear();
+    m_manglers.clear();
     update();
 }
 
@@ -490,7 +493,7 @@ void TextEditorOverlay::mapEquivalentSelections()
 
     QMap<QString, int> all;
     for (int i = 0; i < m_selections.size(); ++i)
-        all.insertMulti(selectionText(i), i);
+        all.insertMulti(selectionText(i).toLower(), i);
 
     const QList<QString> &uniqueKeys = all.uniqueKeys();
     foreach (const QString &key, uniqueKeys) {
@@ -529,6 +532,29 @@ void TextEditorOverlay::updateEquivalentSelections(const QTextCursor &cursor)
     }
 }
 
+void TextEditorOverlay::setNameMangler(const QList<NameMangler *> &manglers)
+{
+    m_manglers = manglers;
+}
+
+void TextEditorOverlay::mangle()
+{
+    for (int i = 0; i < m_manglers.count(); ++i) {
+        if (!m_manglers.at(i))
+            continue;
+
+        const QString current = selectionText(i);
+        const QString result = m_manglers.at(i)->mangle(current);
+        if (result != current) {
+            QTextCursor selectionCursor = assembleCursorForSelection(i);
+            selectionCursor.joinPreviousEditBlock();
+            selectionCursor.removeSelectedText();
+            selectionCursor.insertText(result);
+            selectionCursor.endEditBlock();
+        }
+    }
+}
+
 bool TextEditorOverlay::hasFirstSelectionBeginMoved() const
 {
     if (m_firstSelectionOriginalBegin == -1 || m_selections.isEmpty())
diff --git a/src/plugins/texteditor/texteditoroverlay.h b/src/plugins/texteditor/texteditoroverlay.h
index 05a8163038c..0b9391faf0e 100644
--- a/src/plugins/texteditor/texteditoroverlay.h
+++ b/src/plugins/texteditor/texteditoroverlay.h
@@ -39,6 +39,7 @@
 QT_FORWARD_DECLARE_CLASS(QWidget)
 
 namespace TextEditor {
+class NameMangler;
 class BaseTextEditorWidget;
 
 namespace Internal {
@@ -100,6 +101,8 @@ public:
 
     void mapEquivalentSelections();
     void updateEquivalentSelections(const QTextCursor &cursor);
+    void setNameMangler(const QList<NameMangler *> &manglers);
+    void mangle();
 
     bool hasFirstSelectionBeginMoved() const;
 
@@ -120,6 +123,7 @@ private:
     QWidget *m_viewport;
     QList<OverlaySelection> m_selections;
     QVector<QList<int> > m_equivalentSelections;
+    QList<NameMangler *> m_manglers;
 };
 
 } // namespace Internal
-- 
GitLab