Commit b589336e authored by Tobias Hunger's avatar Tobias Hunger

Snippets: Move snippet parsing into the snippet class

* Move the code to parse snippets into the snippet class
* Allow to escape $ so that this character can be used in
  snippets
* Add unit tests for the snippet parsing

Change-Id: I134f3c0de8290e1d7fcaf808577b31f5ac8fbc63
Reviewed-by: default avatarDavid Schulz <david.schulz@digia.com>
parent 6ac34146
......@@ -35,6 +35,7 @@
#include "behaviorsettings.h"
#include "codecselector.h"
#include "completionsettings.h"
#include "snippets/snippet.h"
#include "tabsettings.h"
#include "typingsettings.h"
#include "icodestylepreferences.h"
......@@ -1931,57 +1932,19 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
{
if ((snippet.count(Snippet::kVariableDelimiter) % 2) != 0) {
qWarning() << "invalid snippet";
return;
}
QList<QTextEdit::ExtraSelection> selections;
Snippet::ParsedSnippet data = Snippet::parse(snippet);
QTextCursor cursor = cursor_arg;
cursor.beginEditBlock();
cursor.removeSelectedText();
const int startCursorPosition = cursor.position();
int pos = 0;
QMap<int, int> positions;
while (pos < snippet.size()) {
if (snippet.at(pos) != Snippet::kVariableDelimiter) {
const int start = pos;
do { ++pos; }
while (pos < snippet.size() && snippet.at(pos) != Snippet::kVariableDelimiter);
cursor.insertText(snippet.mid(start, pos - start));
} else {
// the start of a place holder.
const int start = ++pos;
for (; pos < snippet.size(); ++pos) {
if (snippet.at(pos) == Snippet::kVariableDelimiter)
break;
}
Q_ASSERT(pos < snippet.size());
Q_ASSERT(snippet.at(pos) == Snippet::kVariableDelimiter);
const QString textToInsert = snippet.mid(start, pos - start);
int cursorPosition = cursor.position();
cursor.insertText(textToInsert);
if (textToInsert.isEmpty())
positions.insert(cursorPosition, 0);
else
positions.insert(cursorPosition, textToInsert.length());
++pos;
}
}
cursor.insertText(data.text);
QList<QTextEdit::ExtraSelection> selections;
QMapIterator<int,int> it(positions);
while (it.hasNext()) {
it.next();
int length = it.value();
int position = it.key();
for (int i = 0; i < data.ranges.count(); ++i) {
int position = data.ranges.at(i).start + startCursorPosition;
int length = data.ranges.at(i).length;
QTextCursor tc(document());
tc.setPosition(position);
......
......@@ -142,3 +142,118 @@ QString Snippet::generateTip() const
return tip;
}
Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
{
Snippet::ParsedSnippet result;
result.success = true;
const int count = snippet.count();
bool success = true;
int start = -1;
result.text.reserve(count);
for (int i = 0; i < count; ++i) {
QChar current = snippet.at(i);
QChar next = (i + 1) < count ? snippet.at(i + 1) : QChar();
if (current == QLatin1Char('\\')) {
if (next.isNull()) {
success = false;
break;
}
result.text.append(next);
++i;
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);
}
if (start >= 0)
success = false;
result.success = success;
if (!success) {
result.ranges.clear();
result.text = snippet;
}
return result;
}
#ifdef WITH_TESTS
# include <QTest>
# include "texteditorplugin.h"
void Internal::TextEditorPlugin::testSnippetParsing_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("text");
QTest::addColumn<bool>("success");
QTest::addColumn<QList<int> >("ranges_start");
QTest::addColumn<QList<int> >("ranges_length");
QTest::newRow("no input")
<< QString() << QString() << true << (QList<int>()) << (QList<int>());
QTest::newRow("empty input")
<< QString::fromLatin1("") << QString::fromLatin1("") << true << (QList<int>()) << (QList<int>());
QTest::newRow("simple identifier")
<< QString::fromLatin1("$test$") << QString::fromLatin1("test") << true
<< (QList<int>() << 0) << (QList<int>() << 4);
QTest::newRow("escaped string")
<< QString::fromLatin1("\\$test\\$") << QString::fromLatin1("$test$") << true
<< (QList<int>()) << (QList<int>());
QTest::newRow("escaped escape")
<< QString::fromLatin1("\\\\$test\\\\$") << QString::fromLatin1("\\test\\") << true
<< (QList<int>() << 1) << (QList<int>() << 5);
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 setname NOTIFY nameChanged)") << true
<< (QList<int>() << 11 << 16 << 26 << 40 << 52)
<< (QList<int>() << 4 << 4 << 4 << 4 << 4);
QTest::newRow("broken escape")
<< QString::fromLatin1("\\\\$test\\\\$\\") << QString::fromLatin1("\\\\$test\\\\$\\") << false
<< (QList<int>()) << (QList<int>());
QTest::newRow("open identifier")
<< QString::fromLatin1("$test") << QString::fromLatin1("$test") << false
<< (QList<int>()) << (QList<int>());
}
void Internal::TextEditorPlugin::testSnippetParsing()
{
QFETCH(QString, input);
QFETCH(QString, text);
QFETCH(bool, success);
QFETCH(QList<int>, ranges_start);
QFETCH(QList<int>, ranges_length);
Q_ASSERT(ranges_start.count() == ranges_length.count()); // sanity check for the test data
Snippet::ParsedSnippet result = Snippet::parse(input);
QCOMPARE(result.text, text);
QCOMPARE(result.success, success);
QCOMPARE(result.ranges.count(), ranges_start.count());
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));
}
}
#endif
......@@ -33,6 +33,7 @@
#include <texteditor/texteditor_global.h>
#include <QChar>
#include <QList>
#include <QString>
namespace TextEditor {
......@@ -67,6 +68,20 @@ public:
static const QChar kVariableDelimiter;
class ParsedSnippet {
public:
QString text;
bool success;
struct Range {
Range(int s, int l) : start(s), length(l) { }
int start;
int length;
};
QList<Range> ranges;
};
static ParsedSnippet parse(const QString &snippet);
private:
bool m_isRemoved;
bool m_isModified;
......
......@@ -77,6 +77,11 @@ private slots:
void updateVariable(const QByteArray &variable);
void updateCurrentSelection(const QString &text);
#ifdef WITH_TESTS
void testSnippetParsing_data();
void testSnippetParsing();
#endif
private:
static TextEditorPlugin *m_instance;
TextEditorSettings *m_settings;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment