1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "snippetscollection.h"
35 #include "isnippetprovider.h"
38 #include <coreplugin/icore.h>
39 #include <extensionsystem/pluginmanager.h>
41 #include <QtCore/QLatin1String>
42 #include <QtCore/QFile>
43 #include <QtCore/QFileInfo>
44 #include <QtCore/QDir>
45 #include <QtCore/QDebug>
46 #include <QtCore/QXmlStreamReader>
47 #include <QtAlgorithms>
52 using namespace TextEditor;
53 using namespace Internal;
59 bool operator()(const Snippet &a, const Snippet &b) const
61 const int comp = a.trigger().toLower().localeAwareCompare(b.trigger().toLower());
65 a.complement().toLower().localeAwareCompare(b.complement().toLower()) < 0)
70 SnippetComp snippetComp;
72 struct RemovedSnippetPred
74 bool operator()(const Snippet &s) const
79 RemovedSnippetPred removedSnippetPred;
83 const QLatin1String SnippetsCollection::kSnippet("snippet");
84 const QLatin1String SnippetsCollection::kSnippets("snippets");
85 const QLatin1String SnippetsCollection::kTrigger("trigger");
86 const QLatin1String SnippetsCollection::kId("id");
87 const QLatin1String SnippetsCollection::kComplement("complement");
88 const QLatin1String SnippetsCollection::kGroup("group");
89 const QLatin1String SnippetsCollection::kRemoved("removed");
90 const QLatin1String SnippetsCollection::kModified("modified");
93 SnippetsCollection::Hint::Hint(int index) : m_index(index)
96 SnippetsCollection::Hint::Hint(int index, QList<Snippet>::iterator it) : m_index(index), m_it(it)
99 int SnippetsCollection::Hint::index() const
104 SnippetsCollection *SnippetsCollection::instance()
106 static SnippetsCollection collection;
110 // SnippetsCollection
111 SnippetsCollection::SnippetsCollection() :
112 m_userSnippetsPath(Core::ICore::instance()->userResourcePath() + QLatin1String("/snippets/")),
113 m_userSnippetsFile(QLatin1String("snippets.xml"))
115 QDir dir(Core::ICore::instance()->resourcePath() + QLatin1String("/snippets/"));
116 dir.setNameFilters(QStringList(QLatin1String("*.xml")));
117 foreach (const QFileInfo &fi, dir.entryInfoList())
118 m_builtInSnippetsFiles.append(fi.absoluteFilePath());
120 connect(Core::ICore::instance(), SIGNAL(coreOpened()), this, SLOT(identifyGroups()));
123 SnippetsCollection::~SnippetsCollection()
126 void SnippetsCollection::insertSnippet(const Snippet &snippet)
128 insertSnippet(snippet, computeInsertionHint(snippet));
131 void SnippetsCollection::insertSnippet(const Snippet &snippet, const Hint &hint)
133 const int group = groupIndex(snippet.groupId());
134 if (snippet.isBuiltIn() && snippet.isRemoved()) {
135 m_activeSnippetsEnd[group] = m_snippets[group].insert(m_activeSnippetsEnd[group], snippet);
137 m_snippets[group].insert(hint.m_it, snippet);
138 updateActiveSnippetsEnd(group);
142 SnippetsCollection::Hint SnippetsCollection::computeInsertionHint(const Snippet &snippet)
144 const int group = groupIndex(snippet.groupId());
145 QList<Snippet> &snippets = m_snippets[group];
146 QList<Snippet>::iterator it = qUpperBound(
147 snippets.begin(), m_activeSnippetsEnd.at(group), snippet, snippetComp);
148 return Hint(static_cast<int>(std::distance(snippets.begin(), it)), it);
151 void SnippetsCollection::replaceSnippet(int index, const Snippet &snippet)
153 replaceSnippet(index, snippet, computeReplacementHint(index, snippet));
156 void SnippetsCollection::replaceSnippet(int index, const Snippet &snippet, const Hint &hint)
158 const int group = groupIndex(snippet.groupId());
159 Snippet replacement(snippet);
160 if (replacement.isBuiltIn() && !replacement.isModified())
161 replacement.setIsModified(true);
163 if (index == hint.index()) {
164 m_snippets[group][index] = replacement;
166 insertSnippet(replacement, hint);
167 // Consider whether the row moved up towards the beginning or down towards the end.
168 if (index < hint.index())
169 m_snippets[group].removeAt(index);
171 m_snippets[group].removeAt(index + 1);
172 updateActiveSnippetsEnd(group);
176 SnippetsCollection::Hint SnippetsCollection::computeReplacementHint(int index,
177 const Snippet &snippet)
179 const int group = groupIndex(snippet.groupId());
180 QList<Snippet> &snippets = m_snippets[group];
181 QList<Snippet>::iterator it = qLowerBound(
182 snippets.begin(), m_activeSnippetsEnd.at(group), snippet, snippetComp);
183 int hintIndex = static_cast<int>(std::distance(snippets.begin(), it));
184 if (index < hintIndex - 1)
185 return Hint(hintIndex - 1, it);
186 it = qUpperBound(it, m_activeSnippetsEnd.at(group), snippet, snippetComp);
187 hintIndex = static_cast<int>(std::distance(snippets.begin(), it));
188 if (index > hintIndex)
189 return Hint(hintIndex, it);
190 // Even if the snipet is at a different index it is still inside a valid range.
194 void SnippetsCollection::removeSnippet(int index, const QString &groupId)
196 const int group = groupIndex(groupId);
197 Snippet snippet(m_snippets.at(group).at(index));
198 m_snippets[group].removeAt(index);
199 if (snippet.isBuiltIn()) {
200 snippet.setIsRemoved(true);
201 m_activeSnippetsEnd[group] = m_snippets[group].insert(m_activeSnippetsEnd[group], snippet);
203 updateActiveSnippetsEnd(group);
207 const Snippet &SnippetsCollection::snippet(int index, const QString &groupId) const
209 return m_snippets.at(groupIndex(groupId)).at(index);
212 void SnippetsCollection::setSnippetContent(int index,
213 const QString &groupId,
214 const QString &content)
216 Snippet &snippet = m_snippets[groupIndex(groupId)][index];
217 snippet.setContent(content);
218 if (snippet.isBuiltIn() && !snippet.isModified())
219 snippet.setIsModified(true);
222 int SnippetsCollection::totalActiveSnippets(const QString &groupId) const
224 const int group = groupIndex(groupId);
225 return std::distance<QList<Snippet>::const_iterator>(m_snippets.at(group).begin(),
226 m_activeSnippetsEnd.at(group));
229 int SnippetsCollection::totalSnippets(const QString &groupId) const
231 return m_snippets.at(groupIndex(groupId)).size();
234 QList<QString> SnippetsCollection::groupIds() const
236 return m_groupIndexById.keys();
239 void SnippetsCollection::clearSnippets()
241 for (int group = 0; group < m_groupIndexById.size(); ++group)
242 clearSnippets(group);
245 void SnippetsCollection::clearSnippets(int groupIndex)
247 m_snippets[groupIndex].clear();
248 m_activeSnippetsEnd[groupIndex] = m_snippets[groupIndex].end();
251 void SnippetsCollection::updateActiveSnippetsEnd(int groupIndex)
253 m_activeSnippetsEnd[groupIndex] = std::find_if(m_snippets[groupIndex].begin(),
254 m_snippets[groupIndex].end(),
258 void SnippetsCollection::restoreRemovedSnippets(const QString &groupId)
260 // The version restored contains the last modifications (if any) by the user.
261 // Reverting the snippet can still bring it to the original version
262 const int group = groupIndex(groupId);
263 QVector<Snippet> toRestore(std::distance(m_activeSnippetsEnd[group], m_snippets[group].end()));
264 qCopy(m_activeSnippetsEnd[group], m_snippets[group].end(), toRestore.begin());
265 m_snippets[group].erase(m_activeSnippetsEnd[group], m_snippets[group].end());
266 foreach (Snippet snippet, toRestore) {
267 snippet.setIsRemoved(false);
268 insertSnippet(snippet);
272 Snippet SnippetsCollection::revertedSnippet(int index, const QString &groupId) const
274 const Snippet &candidate = snippet(index, groupId);
275 Q_ASSERT(candidate.isBuiltIn());
277 foreach (const QString &fileName, m_builtInSnippetsFiles) {
278 const QList<Snippet> &builtIn = readXML(fileName, candidate.id());
279 if (builtIn.size() == 1)
280 return builtIn.at(0);
282 return Snippet(groupId);
285 void SnippetsCollection::reset(const QString &groupId)
287 clearSnippets(groupIndex(groupId));
289 const QList<Snippet> &builtInSnippets = allBuiltInSnippets();
290 foreach (const Snippet &snippet, builtInSnippets)
291 if (groupId == snippet.groupId())
292 insertSnippet(snippet);
295 void SnippetsCollection::reload()
299 const QList<Snippet> &builtInSnippets = allBuiltInSnippets();
300 QHash<QString, Snippet> activeBuiltInSnippets;
301 foreach (const Snippet &snippet, builtInSnippets)
302 activeBuiltInSnippets.insert(snippet.id(), snippet);
304 const QList<Snippet> &userSnippets = readXML(m_userSnippetsPath + m_userSnippetsFile);
305 foreach (const Snippet &snippet, userSnippets) {
306 if (snippet.isBuiltIn())
307 // This user snippet overrides the corresponding built-in snippet.
308 activeBuiltInSnippets.remove(snippet.id());
309 insertSnippet(snippet);
312 foreach (const Snippet &snippet, activeBuiltInSnippets)
313 insertSnippet(snippet);
316 void SnippetsCollection::synchronize()
318 if (QFile::exists(m_userSnippetsPath) || QDir().mkpath(m_userSnippetsPath)) {
319 QFile file(m_userSnippetsPath + m_userSnippetsFile);
320 if (file.open(QFile::WriteOnly | QFile::Truncate)) {
321 QXmlStreamWriter writer(&file);
322 writer.setAutoFormatting(true);
323 writer.writeStartDocument();
324 writer.writeStartElement(kSnippets);
325 foreach (const QString &groupId, m_groupIndexById.keys()) {
326 const int size = m_snippets.at(groupIndex(groupId)).size();
327 for (int i = 0; i < size; ++i) {
328 const Snippet ¤t = snippet(i, groupId);
329 if (!current.isBuiltIn() || current.isRemoved() || current.isModified())
330 writeSnippetXML(current, &writer);
333 writer.writeEndElement();
334 writer.writeEndDocument();
342 void SnippetsCollection::writeSnippetXML(const Snippet &snippet, QXmlStreamWriter *writer) const
344 writer->writeStartElement(kSnippet);
345 writer->writeAttribute(kGroup, snippet.groupId());
346 writer->writeAttribute(kTrigger, snippet.trigger());
347 writer->writeAttribute(kId, snippet.id());
348 writer->writeAttribute(kComplement, snippet.complement());
349 writer->writeAttribute(kRemoved, fromBool(snippet.isRemoved()));
350 writer->writeAttribute(kModified, fromBool(snippet.isModified()));
351 writer->writeCharacters(snippet.content());
352 writer->writeEndElement();
355 QList<Snippet> SnippetsCollection::readXML(const QString &fileName, const QString &snippetId) const
357 QList<Snippet> snippets;
358 QFile file(fileName);
359 if (file.exists() && file.open(QIODevice::ReadOnly)) {
360 QXmlStreamReader xml(&file);
361 if (xml.readNextStartElement()) {
362 if (xml.name() == kSnippets) {
363 while (xml.readNextStartElement()) {
364 if (xml.name() == kSnippet) {
365 const QXmlStreamAttributes &atts = xml.attributes();
366 const QString &id = atts.value(kId).toString();
367 const QString &groupId = atts.value(kGroup).toString();
368 if (isGroupKnown(groupId) && (snippetId.isEmpty() || snippetId == id)) {
369 Snippet snippet(groupId, id);
370 snippet.setTrigger(atts.value(kTrigger).toString());
371 snippet.setComplement(atts.value(kComplement).toString());
372 snippet.setIsRemoved(toBool(atts.value(kRemoved).toString()));
373 snippet.setIsModified(toBool(atts.value(kModified).toString()));
376 while (!xml.atEnd()) {
378 if (xml.isCharacters()) {
379 content += xml.text();
380 } else if (xml.isEndElement()) {
381 snippet.setContent(content);
382 snippets.append(snippet);
387 if (!snippetId.isEmpty())
390 xml.skipCurrentElement();
393 xml.skipCurrentElement();
399 qWarning() << fileName << xml.errorString() << xml.lineNumber() << xml.columnNumber();
406 QList<Snippet> SnippetsCollection::allBuiltInSnippets() const
408 QList<Snippet> builtInSnippets;
409 foreach (const QString &fileName, m_builtInSnippetsFiles)
410 builtInSnippets.append(readXML(fileName));
411 return builtInSnippets;
414 int SnippetsCollection::groupIndex(const QString &groupId) const
416 return m_groupIndexById.value(groupId);
419 void SnippetsCollection::identifyGroups()
421 const QList<ISnippetProvider *> &providers =
422 ExtensionSystem::PluginManager::instance()->getObjects<ISnippetProvider>();
423 foreach (ISnippetProvider *provider, providers) {
424 const int groupIndex = m_groupIndexById.size();
425 m_groupIndexById.insert(provider->groupId(), groupIndex);
426 m_snippets.resize(groupIndex + 1);
427 m_activeSnippetsEnd.resize(groupIndex + 1);
428 m_activeSnippetsEnd[groupIndex] = m_snippets[groupIndex].end();
434 bool SnippetsCollection::isGroupKnown(const QString &groupId) const
436 return m_groupIndexById.value(groupId, -1) != -1;