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>
This commit is contained in:
Tobias Hunger
2013-08-09 17:45:14 +02:00
parent b589336e54
commit ee47b652a6
6 changed files with 184 additions and 23 deletions

View File

@@ -71,5 +71,5 @@ case $value$:
default: default:
break; break;
}</snippet> }</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> </snippets>

View File

@@ -1559,6 +1559,7 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
&& d->m_snippetOverlay->isVisible()) { && d->m_snippetOverlay->isVisible()) {
e->accept(); e->accept();
d->m_snippetOverlay->hide(); d->m_snippetOverlay->hide();
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
QTextCursor cursor = textCursor(); QTextCursor cursor = textCursor();
cursor.clearSelection(); cursor.clearSelection();
@@ -1598,6 +1599,7 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
if (d->m_snippetOverlay->isVisible()) { if (d->m_snippetOverlay->isVisible()) {
e->accept(); e->accept();
d->m_snippetOverlay->hide(); d->m_snippetOverlay->hide();
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
QTextCursor cursor = textCursor(); QTextCursor cursor = textCursor();
cursor.movePosition(QTextCursor::EndOfBlock); cursor.movePosition(QTextCursor::EndOfBlock);
@@ -1942,6 +1944,7 @@ void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, cons
cursor.insertText(data.text); cursor.insertText(data.text);
QList<QTextEdit::ExtraSelection> selections; QList<QTextEdit::ExtraSelection> selections;
QList<NameMangler *> manglers;
for (int i = 0; i < data.ranges.count(); ++i) { for (int i = 0; i < data.ranges.count(); ++i) {
int position = data.ranges.at(i).start + startCursorPosition; int position = data.ranges.at(i).start + startCursorPosition;
int length = data.ranges.at(i).length; int length = data.ranges.at(i).length;
@@ -1953,6 +1956,7 @@ void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, cons
selection.cursor = tc; selection.cursor = tc;
selection.format = (length ? d->m_occurrencesFormat : d->m_occurrenceRenameFormat); selection.format = (length ? d->m_occurrencesFormat : d->m_occurrenceRenameFormat);
selections.append(selection); selections.append(selection);
manglers << data.ranges.at(i).mangler;
} }
cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor); cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
@@ -1960,6 +1964,7 @@ void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, cons
cursor.endEditBlock(); cursor.endEditBlock();
setExtraSelections(BaseTextEditorWidget::SnippetPlaceholderSelection, selections); setExtraSelections(BaseTextEditorWidget::SnippetPlaceholderSelection, selections);
d->m_snippetOverlay->setNameMangler(manglers);
if (!selections.isEmpty()) { if (!selections.isEmpty()) {
const QTextEdit::ExtraSelection &selection = selections.first(); const QTextEdit::ExtraSelection &selection = selections.first();
@@ -2516,6 +2521,7 @@ bool BaseTextEditorWidgetPrivate::snippetCheckCursor(const QTextCursor &cursor)
|| !m_snippetOverlay->hasCursorInSelection(end) || !m_snippetOverlay->hasCursorInSelection(end)
|| m_snippetOverlay->hasFirstSelectionBeginMoved()) { || m_snippetOverlay->hasFirstSelectionBeginMoved()) {
m_snippetOverlay->setVisible(false); m_snippetOverlay->setVisible(false);
m_snippetOverlay->mangle();
m_snippetOverlay->clear(); m_snippetOverlay->clear();
return false; return false;
} }
@@ -4696,6 +4702,7 @@ void BaseTextEditorWidget::handleBackspaceKey()
handled = true; handled = true;
} else { } else {
if (cursorWithinSnippet) { if (cursorWithinSnippet) {
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
cursorWithinSnippet = false; cursorWithinSnippet = false;
} }
@@ -4728,6 +4735,7 @@ void BaseTextEditorWidget::handleBackspaceKey()
cursor.deletePreviousChar(); cursor.deletePreviousChar();
} else { } else {
if (cursorWithinSnippet) { if (cursorWithinSnippet) {
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
cursorWithinSnippet = false; cursorWithinSnippet = false;
} }
@@ -5317,6 +5325,7 @@ void BaseTextEditorWidget::setExtraSelections(ExtraSelectionKind kind, const QLi
} }
d->m_overlay->setVisible(!d->m_overlay->isEmpty()); d->m_overlay->setVisible(!d->m_overlay->isEmpty());
} else if (kind == SnippetPlaceholderSelection) { } else if (kind == SnippetPlaceholderSelection) {
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) { foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
d->m_snippetOverlay->addOverlaySelection(selection.cursor, d->m_snippetOverlay->addOverlaySelection(selection.cursor,
@@ -5990,6 +5999,7 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
if (d->m_snippetOverlay->isVisible() && lines.count() > 1) { if (d->m_snippetOverlay->isVisible() && lines.count() > 1) {
d->m_snippetOverlay->hide(); d->m_snippetOverlay->hide();
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
} }
@@ -6006,6 +6016,7 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n')) if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n'))
|| text.contains(QLatin1Char('\t')))) { || text.contains(QLatin1Char('\t')))) {
d->m_snippetOverlay->hide(); d->m_snippetOverlay->hide();
d->m_snippetOverlay->mangle();
d->m_snippetOverlay->clear(); d->m_snippetOverlay->clear();
} }

View File

@@ -29,12 +29,54 @@
#include "snippet.h" #include "snippet.h"
#include <coreplugin/id.h>
#include <QLatin1Char> #include <QLatin1Char>
#include <QLatin1String> #include <QLatin1String>
#include <QTextDocument> #include <QTextDocument>
using namespace TextEditor; 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('$')); const QChar Snippet::kVariableDelimiter(QLatin1Char('$'));
Snippet::Snippet(const QString &groupId, const QString &id) : Snippet::Snippet(const QString &groupId, const QString &id) :
@@ -145,12 +187,17 @@ QString Snippet::generateTip() const
Snippet::ParsedSnippet Snippet::parse(const QString &snippet) Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
{ {
static UppercaseMangler ucMangler;
static LowercaseMangler lcMangler;
static TitlecaseMangler tcMangler;
Snippet::ParsedSnippet result; Snippet::ParsedSnippet result;
result.success = true; result.success = true;
const int count = snippet.count(); const int count = snippet.count();
bool success = true; bool success = true;
int start = -1; int start = -1;
NameMangler *mangler = 0;
result.text.reserve(count); result.text.reserve(count);
@@ -158,6 +205,45 @@ Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
QChar current = snippet.at(i); QChar current = snippet.at(i);
QChar next = (i + 1) < count ? snippet.at(i + 1) : QChar(); 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 (current == QLatin1Char('\\')) {
if (next.isNull()) { if (next.isNull()) {
success = false; success = false;
@@ -168,16 +254,6 @@ Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
continue; 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); result.text.append(current);
} }
@@ -206,34 +282,60 @@ void Internal::TextEditorPlugin::testSnippetParsing_data()
QTest::addColumn<bool>("success"); QTest::addColumn<bool>("success");
QTest::addColumn<QList<int> >("ranges_start"); QTest::addColumn<QList<int> >("ranges_start");
QTest::addColumn<QList<int> >("ranges_length"); QTest::addColumn<QList<int> >("ranges_length");
QTest::addColumn<QList<Core::Id> >("ranges_mangler");
QTest::newRow("no input") 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") 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") QTest::newRow("simple identifier")
<< QString::fromLatin1("$test$") << QString::fromLatin1("test") << true << QString::fromLatin1("$tESt$") << QString::fromLatin1("tESt") << true
<< (QList<int>() << 0) << (QList<int>() << 4); << (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") QTest::newRow("escaped string")
<< QString::fromLatin1("\\$test\\$") << QString::fromLatin1("$test$") << true << QString::fromLatin1("\\$test\\$") << QString::fromLatin1("$test$") << true
<< (QList<int>()) << (QList<int>()); << (QList<int>()) << (QList<int>())
<< (QList<Core::Id>());
QTest::newRow("escaped escape") QTest::newRow("escaped escape")
<< QString::fromLatin1("\\\\$test\\\\$") << QString::fromLatin1("\\test\\") << true << 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") 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 << QString::fromLatin1("Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged)") << true
<< (QList<int>() << 11 << 16 << 26 << 40 << 52) << (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") QTest::newRow("broken escape")
<< QString::fromLatin1("\\\\$test\\\\$\\") << QString::fromLatin1("\\\\$test\\\\$\\") << false << QString::fromLatin1("\\\\$test\\\\$\\") << QString::fromLatin1("\\\\$test\\\\$\\") << false
<< (QList<int>()) << (QList<int>()); << (QList<int>()) << (QList<int>())
<< (QList<Core::Id>());
QTest::newRow("open identifier") QTest::newRow("open identifier")
<< QString::fromLatin1("$test") << QString::fromLatin1("$test") << false << 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() void Internal::TextEditorPlugin::testSnippetParsing()
@@ -243,7 +345,9 @@ void Internal::TextEditorPlugin::testSnippetParsing()
QFETCH(bool, success); QFETCH(bool, success);
QFETCH(QList<int>, ranges_start); QFETCH(QList<int>, ranges_start);
QFETCH(QList<int>, ranges_length); 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_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); Snippet::ParsedSnippet result = Snippet::parse(input);
@@ -253,6 +357,10 @@ void Internal::TextEditorPlugin::testSnippetParsing()
for (int i = 0; i < ranges_start.count(); ++i) { for (int i = 0; i < ranges_start.count(); ++i) {
QCOMPARE(result.ranges.at(i).start, ranges_start.at(i)); QCOMPARE(result.ranges.at(i).start, ranges_start.at(i));
QCOMPARE(result.ranges.at(i).length, ranges_length.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 #endif

View File

@@ -36,8 +36,19 @@
#include <QList> #include <QList>
#include <QString> #include <QString>
namespace Core { class Id; }
namespace TextEditor { 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 class TEXTEDITOR_EXPORT Snippet
{ {
public: public:
@@ -73,9 +84,10 @@ public:
QString text; QString text;
bool success; bool success;
struct Range { 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 start;
int length; int length;
NameMangler *mangler;
}; };
QList<Range> ranges; QList<Range> ranges;
}; };

View File

@@ -29,6 +29,7 @@
#include "texteditoroverlay.h" #include "texteditoroverlay.h"
#include "basetexteditor.h" #include "basetexteditor.h"
#include "snippets/snippet.h"
#include <QDebug> #include <QDebug>
#include <QMap> #include <QMap>
@@ -72,6 +73,8 @@ void TextEditorOverlay::clear()
return; return;
m_selections.clear(); m_selections.clear();
m_firstSelectionOriginalBegin = -1; m_firstSelectionOriginalBegin = -1;
m_equivalentSelections.clear();
m_manglers.clear();
update(); update();
} }
@@ -490,7 +493,7 @@ void TextEditorOverlay::mapEquivalentSelections()
QMap<QString, int> all; QMap<QString, int> all;
for (int i = 0; i < m_selections.size(); ++i) 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(); const QList<QString> &uniqueKeys = all.uniqueKeys();
foreach (const QString &key, 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 bool TextEditorOverlay::hasFirstSelectionBeginMoved() const
{ {
if (m_firstSelectionOriginalBegin == -1 || m_selections.isEmpty()) if (m_firstSelectionOriginalBegin == -1 || m_selections.isEmpty())

View File

@@ -39,6 +39,7 @@
QT_FORWARD_DECLARE_CLASS(QWidget) QT_FORWARD_DECLARE_CLASS(QWidget)
namespace TextEditor { namespace TextEditor {
class NameMangler;
class BaseTextEditorWidget; class BaseTextEditorWidget;
namespace Internal { namespace Internal {
@@ -100,6 +101,8 @@ public:
void mapEquivalentSelections(); void mapEquivalentSelections();
void updateEquivalentSelections(const QTextCursor &cursor); void updateEquivalentSelections(const QTextCursor &cursor);
void setNameMangler(const QList<NameMangler *> &manglers);
void mangle();
bool hasFirstSelectionBeginMoved() const; bool hasFirstSelectionBeginMoved() const;
@@ -120,6 +123,7 @@ private:
QWidget *m_viewport; QWidget *m_viewport;
QList<OverlaySelection> m_selections; QList<OverlaySelection> m_selections;
QVector<QList<int> > m_equivalentSelections; QVector<QList<int> > m_equivalentSelections;
QList<NameMangler *> m_manglers;
}; };
} // namespace Internal } // namespace Internal