From 5b8a6786720a839f8fade2dcf9d24d4e48d580a4 Mon Sep 17 00:00:00 2001
From: mae <qt-info@nokia.com>
Date: Tue, 19 Jan 2010 18:50:50 +0100
Subject: [PATCH] first iteration of snippet support for qml

Done with Thorbjorn and Roberto
---
 share/qtcreator/snippets/qml.xml              |  96 ++++++++++++++
 src/plugins/qmljseditor/qmlcodecompletion.cpp | 124 ++++++++++--------
 src/plugins/qmljseditor/qmlcodecompletion.h   |   5 +
 src/plugins/texteditor/basetexteditor.cpp     |  53 ++++++--
 src/plugins/texteditor/texteditoroverlay.h    |   3 +
 5 files changed, 214 insertions(+), 67 deletions(-)
 create mode 100644 share/qtcreator/snippets/qml.xml

diff --git a/share/qtcreator/snippets/qml.xml b/share/qtcreator/snippets/qml.xml
new file mode 100644
index 00000000000..4a2c3a5f2ea
--- /dev/null
+++ b/share/qtcreator/snippets/qml.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<snippets>
+<snippet>property <tab>name</tab> <tab>name</tab> : <tab>name</tab>
+</snippet>
+<snippet>Item {
+	id: <tab>name</tab>
+	
+	}
+</snippet>
+<snippet>BorderImage {
+    id: <tab>name</tab>
+    width: <tab>name</tab>; height: <tab>name</tab>
+    border.left: <tab>name</tab>; border.top: <tab>name</tab>
+    border.right: <tab>name</tab>; border.bottom: <tab>name</tab>
+    source: "<tab>name</tab>"
+}
+</snippet>
+
+<snippet>Image {
+    id: <tab>name</tab>
+    source: "<tab>name</tab>"
+}
+</snippet>
+
+<snippet>Text {
+    id: <tab>name</tab>
+    text: "<tab>name</tab>"
+}
+</snippet>
+
+<snippet>states: [
+    State {
+        name: "<tab>name</tab>"
+        PropertyChanges {
+            target: <tab>name</tab>
+            <tab/>
+        }
+    }
+]
+</snippet>
+
+<snippet>State {
+    name: "<tab>name</tab>"
+    PropertyChanges {
+        target: <tab>name</tab>
+        <tab/>
+    }
+}
+</snippet>
+
+<snippet>transitions: [
+    Transition {
+        from: "<tab>name</tab>"
+        to: "<tab>name</tab>"
+        <tab/>
+     }
+]
+</snippet>
+
+<snippet>Transition {
+    from: "<tab>name</tab>"
+    to: "<tab>name</tab>"
+    <tab/>
+}
+</snippet>
+
+<snippet>PropertyChanges {
+        target: <tab>name</tab>
+        <tab/>
+    }
+</snippet>
+
+<snippet description="matchTargets">NumberAnimation { matchTargets: "<tab>name</tab>"; matchProperties: "<tab>name</tab>"; duration: <tab>200</tab> }
+</snippet>
+<snippet description="target">NumberAnimation { target: "<tab>name</tab>"; property: "<tab>name</tab>"; value: <tab>name</tab>; duration: <tab>200</tab> }
+</snippet>
+<snippet>PropertyAction {  matchTargets: "<tab>name</tab>"; matchProperties: "<tab>name</tab>"; duration: <tab>200</tab> }
+</snippet>
+<snippet>PropertyAction { target: "<tab>name</tab>"; property: "<tab>name</tab>"; value: <tab>name</tab>; duration: <tab>200</tab> }
+</snippet>
+<snippet>PauseAnimation { duration: <tab>name</tab>}
+</snippet>
+<snippet>ColorAnimation { from: <tab>name</tab>; to: <tab>name</tab>; duration: <tab>200</tab> }
+</snippet>
+<snippet>effect: Colorize { color: "<tab>name</tab>" }
+</snippet>
+<snippet>effect: Blur { blurRadius: "<tab>200</tab>" }
+</snippet>
+<snippet>effect: DropShadow {
+            blurRadius: <tab>200</tab>
+            offset.x: <tab>200</tab>
+            offset.y: <tab>200</tab>
+        }
+</snippet>
+
+</snippets>
diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp
index af7391854ba..56376c13c0e 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.cpp
+++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp
@@ -37,6 +37,12 @@
 #include <qmljs/qmljssymbol.h>
 #include <texteditor/basetexteditor.h>
 
+#include <coreplugin/icore.h>
+
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QXmlStreamReader>
+
 #include <QtDebug>
 
 using namespace QmlJSEditor;
@@ -134,61 +140,9 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
         }
     }
 
+    updateSnippets();
 
-    // snippets completion
-    TextEditor::CompletionItem item(this);
-    item.text = QLatin1String("Rectangle - declaration");
-    item.data = QVariant::fromValue(QString("Rectangle {\nwidth: $100$;\nheight: 100;\n$$\n}"));
-    m_completions.append(item);
-
-    item.text = QLatin1String("Item - declaration");
-    item.data = QVariant::fromValue(QString("Item {\nwidth: $100$;\nheight: 100;\n$$\n}"));
-    m_completions.append(item);
-
-    item.text = QLatin1String("BorderImage - declaration");
-    item.data = QLatin1String("BorderImage {\n"
-                              "id: $name$;\n"
-                              "width: $100$; height: $100$;\n"
-                              "border.left: $2$; border.top: $2$;\n"
-                              "border.right: $2$; border.bottom: $2$;\n"
-                              "source: \"$name$\";\n"
-                              "}\n");
-    m_completions.append(item);
-
-    item.text = QLatin1String("State - declaration");
-    item.data = QLatin1String("State {\n"
-                              "name: \"$state$;\"\n"
-                              "PropertyChanges {\n"
-                              "target: $target$;\n"
-                              "$$\n"
-                              "}\n"
-                              "}\n");
-    m_completions.append(item);
-
-    item.text = QLatin1String("states - declaration");
-    item.data = QLatin1String("states: [\n"
-                              "State {\n"
-                              "name: \"$state$\";\n"
-                              "PropertyChanges {\n"
-                              "target: $target$;\n"
-                              "$$\n"
-                              "}\n"
-                              "}\n"
-                              "]\n");
-    m_completions.append(item);
-
-    item.text = QLatin1String("property - declaration");
-    item.data = QLatin1String("property $var$ $name$: $value$;\n");
-    m_completions.append(item);
-
-    item.text = QLatin1String("readonly property - declaration");
-    item.data = QLatin1String("readonly property $var$ $name$: $value$;\n");
-    m_completions.append(item);
-
-    item.text = QLatin1String("NumericAnimation - declaration");
-    item.data = QLatin1String("NumberAnimation { matchTargets: \"$target$\"; matchProperties: \"$properties$\"; duration: $1000$ }\n");
-    m_completions.append(item);
-
+    m_completions.append(m_snippets);
     return pos;
 }
 
@@ -299,3 +253,65 @@ void QmlCodeCompletion::cleanup()
     m_completions.clear();
 }
 
+
+void QmlCodeCompletion::updateSnippets()
+{
+    QString qmlsnippets = Core::ICore::instance()->resourcePath() + QLatin1String("/snippets/qml.xml");
+    if (!QFile::exists(qmlsnippets))
+        return;
+
+    QDateTime lastModified = QFileInfo(qmlsnippets).lastModified();
+    if (!m_snippetFileLastModified.isNull() &&  lastModified == m_snippetFileLastModified)
+        return;
+
+    m_snippetFileLastModified = lastModified;
+    QFile file(qmlsnippets);
+    file.open(QIODevice::ReadOnly);
+    QXmlStreamReader xml(&file);
+    while (!xml.atEnd() && xml.readNextStartElement()) {
+        if (xml.name() == QLatin1String("snippets")) {
+            while (xml.readNextStartElement()) {
+                if (xml.name() == QLatin1String("snippet")) {
+                    TextEditor::CompletionItem item(this);
+                    QString title, data;
+                    QString description = xml.attributes().value("description").toString();
+
+                    while (!xml.atEnd()) {
+                        xml.readNext();
+                        if (xml.isEndElement()) {
+                            int i = 0;
+                            while (i < data.size() && data.at(i).isLetterOrNumber())
+                                ++i;
+                            title = data.left(i);
+                            item.text = title;
+                            if (!description.isEmpty()) {
+                                item.text +=  QLatin1Char(' ');
+                                item.text += description;
+                            }
+                            item.data = QVariant::fromValue(data);
+                            m_snippets.append(item);
+                            break;
+
+                        }
+
+                        if (xml.isCharacters())
+                            data += xml.text();
+                        else if (xml.isStartElement()) {
+                            if (xml.name() != QLatin1String("tab"))
+                                xml.raiseError(QLatin1String("invalid snippets file"));
+                            else {
+                                data += QChar::ObjectReplacementCharacter;
+                                data += xml.readElementText();
+                                data += QChar::ObjectReplacementCharacter;
+                            }
+                        }
+
+                    }
+                }
+            }
+        }
+    }
+    if (xml.hasError())
+        qWarning() << qmlsnippets << xml.errorString();
+    file.close();
+}
diff --git a/src/plugins/qmljseditor/qmlcodecompletion.h b/src/plugins/qmljseditor/qmlcodecompletion.h
index cb8687cde66..8adc066fc9f 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.h
+++ b/src/plugins/qmljseditor/qmlcodecompletion.h
@@ -32,6 +32,7 @@
 
 #include <qmljs/qmljstypesystem.h>
 #include <texteditor/icompletioncollector.h>
+#include <QtCore/QDateTime>
 
 namespace TextEditor {
 class ITextEditable;
@@ -69,6 +70,10 @@ private:
     QList<TextEditor::CompletionItem> m_completions;
     Qt::CaseSensitivity m_caseSensitivity;
     QmlJS::TypeSystem *m_typeSystem;
+
+    QList<TextEditor::CompletionItem> m_snippets;
+    QDateTime m_snippetFileLastModified;
+    void updateSnippets();
 };
 
 
diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp
index 7fc9eaf18fa..718e757803d 100644
--- a/src/plugins/texteditor/basetexteditor.cpp
+++ b/src/plugins/texteditor/basetexteditor.cpp
@@ -960,11 +960,15 @@ void BaseTextEditor::keyPressEvent(QKeyEvent *e)
     d->m_lastEventWasBlockSelectionEvent = false;
 
     if (e->key() == Qt::Key_Escape) {
-        e->accept();
-        QTextCursor cursor = textCursor();
-        cursor.clearSelection();
-        setTextCursor(cursor);
-        return;
+        if (d->m_snippetOverlay->isVisible()) {
+            e->accept();
+            d->m_snippetOverlay->hide();
+            d->m_snippetOverlay->clear();
+            QTextCursor cursor = textCursor();
+            cursor.clearSelection();
+            setTextCursor(cursor);
+            return;
+        }
     }
 
     bool ro = isReadOnly();
@@ -996,6 +1000,17 @@ void BaseTextEditor::keyPressEvent(QKeyEvent *e)
             || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))
         ) {
 
+        if (d->m_snippetOverlay->isVisible()) {
+            e->accept();
+            d->m_snippetOverlay->hide();
+            d->m_snippetOverlay->clear();
+            QTextCursor cursor = textCursor();
+            cursor.movePosition(QTextCursor::EndOfBlock);
+            setTextCursor(cursor);
+            return;
+        }
+
+
         QTextCursor cursor = textCursor();
         if (d->m_inBlockSelectionMode)
             cursor.clearSelection();
@@ -1190,9 +1205,10 @@ void BaseTextEditor::keyPressEvent(QKeyEvent *e)
         return;
     }
 
-    if (d->m_snippetOverlay->isVisible() &&
-        (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace))
+    if (d->m_snippetOverlay->isVisible()
+        && (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)) {
         d->snippetCheckCursor(textCursor());
+    }
 
     if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) {
         QPlainTextEdit::keyPressEvent(e);
@@ -1263,21 +1279,21 @@ void BaseTextEditor::insertCodeSnippet(const QString &snippet)
     QMap<int, int> positions;
 
     while (pos < snippet.size()) {
-        if (snippet.at(pos) != QLatin1Char('$')) {
+        if (snippet.at(pos) != QChar::ObjectReplacementCharacter) {
             const int start = pos;
             do { ++pos; }
-            while (pos < snippet.size() && snippet.at(pos) != QLatin1Char('$'));
+            while (pos < snippet.size() && snippet.at(pos) != QChar::ObjectReplacementCharacter);
             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) == QLatin1Char('$'))
+                if (snippet.at(pos) == QChar::ObjectReplacementCharacter)
                     break;
             }
 
             Q_ASSERT(pos < snippet.size());
-            Q_ASSERT(snippet.at(pos) == QLatin1Char('$'));
+            Q_ASSERT(snippet.at(pos) == QChar::ObjectReplacementCharacter);
 
             const QString textToInsert = snippet.mid(start, pos - start);
 
@@ -1319,7 +1335,12 @@ void BaseTextEditor::insertCodeSnippet(const QString &snippet)
         const QTextEdit::ExtraSelection &selection = selections.first();
 
         cursor = textCursor();
-        cursor.setPosition(selection.cursor.anchor() + 1);
+        if (selection.cursor.hasSelection()) {
+            cursor.setPosition(selection.cursor.selectionStart()+1);
+            cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor);
+        } else {
+            cursor.setPosition(selection.cursor.position());
+        }
         setTextCursor(cursor);
     }
 }
@@ -1416,8 +1437,13 @@ bool BaseTextEditor::event(QEvent *e)
     d->m_contentsChanged = false;
     switch (e->type()) {
     case QEvent::ShortcutOverride:
+        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && d->m_snippetOverlay->isVisible()) {
+            e->accept();
+            return true;
+        }
         e->ignore(); // we are a really nice citizen
         return true;
+        break;
     default:
         break;
     }
@@ -1746,7 +1772,8 @@ void BaseTextEditorPrivate::snippetTabOrBacktab(bool forward)
     if (forward) {
         for (int i = 0; i < m_snippetOverlay->m_selections.count(); ++i){
             const OverlaySelection &selection = m_snippetOverlay->m_selections.at(i);
-            if (selection.m_cursor_begin.position() > cursor.position()) {
+            if (selection.m_cursor_begin.position() >= cursor.position()
+                && selection.m_cursor_end.position() > cursor.position()) {
                 final = selection;
                 break;
             }
diff --git a/src/plugins/texteditor/texteditoroverlay.h b/src/plugins/texteditor/texteditoroverlay.h
index 5e8c6d76874..9f98c38c9ac 100644
--- a/src/plugins/texteditor/texteditoroverlay.h
+++ b/src/plugins/texteditor/texteditoroverlay.h
@@ -72,6 +72,9 @@ public:
     bool isVisible() const { return m_visible; }
     void setVisible(bool b);
 
+    inline void hide() { setVisible(false); }
+    inline void show() { setVisible(true); }
+
     void setBorderWidth(int bw) {m_borderWidth = bw; }
 
     void update();
-- 
GitLab