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