OSDN Git Service

352b5dc47d33895a8f9875e2d47a64633ca92ba8
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / snippets / snippetscollection.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "snippetscollection.h"
35 #include "isnippetprovider.h"
36 #include "reuse.h"
37
38 #include <coreplugin/icore.h>
39 #include <extensionsystem/pluginmanager.h>
40
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>
48
49 #include <iterator>
50 #include <algorithm>
51
52 using namespace TextEditor;
53 using namespace Internal;
54
55 namespace {
56
57 struct SnippetComp
58 {
59     bool operator()(const Snippet &a, const Snippet &b) const
60     {
61         const int comp = a.trigger().toLower().localeAwareCompare(b.trigger().toLower());
62         if (comp < 0)
63             return true;
64         else if (comp == 0 &&
65                  a.complement().toLower().localeAwareCompare(b.complement().toLower()) < 0)
66             return true;
67         return false;
68     }
69 };
70 SnippetComp snippetComp;
71
72 struct RemovedSnippetPred
73 {
74     bool operator()(const Snippet &s) const
75     {
76         return s.isRemoved();
77     }
78 };
79 RemovedSnippetPred removedSnippetPred;
80
81 } // Anonymous
82
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");
91
92 // Hint
93 SnippetsCollection::Hint::Hint(int index) : m_index(index)
94 {}
95
96 SnippetsCollection::Hint::Hint(int index, QList<Snippet>::iterator it) : m_index(index), m_it(it)
97 {}
98
99 int SnippetsCollection::Hint::index() const
100 {
101     return m_index;
102 }
103
104 SnippetsCollection *SnippetsCollection::instance()
105 {
106     static SnippetsCollection collection;
107     return &collection;
108 }
109
110 // SnippetsCollection
111 SnippetsCollection::SnippetsCollection() :
112     m_userSnippetsPath(Core::ICore::instance()->userResourcePath() + QLatin1String("/snippets/")),
113     m_userSnippetsFile(QLatin1String("snippets.xml"))
114 {
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());
119
120     connect(Core::ICore::instance(), SIGNAL(coreOpened()), this, SLOT(identifyGroups()));
121 }
122
123 SnippetsCollection::~SnippetsCollection()
124 {}
125
126 void SnippetsCollection::insertSnippet(const Snippet &snippet)
127 {
128     insertSnippet(snippet, computeInsertionHint(snippet));
129 }
130
131 void SnippetsCollection::insertSnippet(const Snippet &snippet, const Hint &hint)
132 {
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);
136     } else {
137         m_snippets[group].insert(hint.m_it, snippet);
138         updateActiveSnippetsEnd(group);
139     }
140 }
141
142 SnippetsCollection::Hint SnippetsCollection::computeInsertionHint(const Snippet &snippet)
143 {
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);
149 }
150
151 void SnippetsCollection::replaceSnippet(int index, const Snippet &snippet)
152 {
153     replaceSnippet(index, snippet, computeReplacementHint(index, snippet));
154 }
155
156 void SnippetsCollection::replaceSnippet(int index, const Snippet &snippet, const Hint &hint)
157 {
158     const int group = groupIndex(snippet.groupId());
159     Snippet replacement(snippet);
160     if (replacement.isBuiltIn() && !replacement.isModified())
161         replacement.setIsModified(true);
162
163     if (index == hint.index()) {
164         m_snippets[group][index] = replacement;
165     } else {
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);
170         else
171             m_snippets[group].removeAt(index + 1);
172         updateActiveSnippetsEnd(group);
173     }
174 }
175
176 SnippetsCollection::Hint SnippetsCollection::computeReplacementHint(int index,
177                                                                     const Snippet &snippet)
178 {
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.
191     return Hint(index);
192 }
193
194 void SnippetsCollection::removeSnippet(int index, const QString &groupId)
195 {
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);
202     } else {
203         updateActiveSnippetsEnd(group);
204     }
205 }
206
207 const Snippet &SnippetsCollection::snippet(int index, const QString &groupId) const
208 {
209     return m_snippets.at(groupIndex(groupId)).at(index);
210 }
211
212 void SnippetsCollection::setSnippetContent(int index,
213                                            const QString &groupId,
214                                            const QString &content)
215 {
216     Snippet &snippet = m_snippets[groupIndex(groupId)][index];
217     snippet.setContent(content);
218     if (snippet.isBuiltIn() && !snippet.isModified())
219         snippet.setIsModified(true);
220 }
221
222 int SnippetsCollection::totalActiveSnippets(const QString &groupId) const
223 {
224     const int group = groupIndex(groupId);
225     return std::distance<QList<Snippet>::const_iterator>(m_snippets.at(group).begin(),
226                                                          m_activeSnippetsEnd.at(group));
227 }
228
229 int SnippetsCollection::totalSnippets(const QString &groupId) const
230 {
231     return m_snippets.at(groupIndex(groupId)).size();
232 }
233
234 QList<QString> SnippetsCollection::groupIds() const
235 {
236     return m_groupIndexById.keys();
237 }
238
239 void SnippetsCollection::clearSnippets()
240 {
241     for (int group = 0; group < m_groupIndexById.size(); ++group)
242         clearSnippets(group);
243 }
244
245 void SnippetsCollection::clearSnippets(int groupIndex)
246 {
247     m_snippets[groupIndex].clear();
248     m_activeSnippetsEnd[groupIndex] = m_snippets[groupIndex].end();
249 }
250
251 void SnippetsCollection::updateActiveSnippetsEnd(int groupIndex)
252 {
253     m_activeSnippetsEnd[groupIndex] = std::find_if(m_snippets[groupIndex].begin(),
254                                                    m_snippets[groupIndex].end(),
255                                                    removedSnippetPred);
256 }
257
258 void SnippetsCollection::restoreRemovedSnippets(const QString &groupId)
259 {
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);
269     }
270 }
271
272 Snippet SnippetsCollection::revertedSnippet(int index, const QString &groupId) const
273 {
274     const Snippet &candidate = snippet(index, groupId);
275     Q_ASSERT(candidate.isBuiltIn());
276
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);
281     }
282     return Snippet(groupId);
283 }
284
285 void SnippetsCollection::reset(const QString &groupId)
286 {
287     clearSnippets(groupIndex(groupId));
288
289     const QList<Snippet> &builtInSnippets = allBuiltInSnippets();
290     foreach (const Snippet &snippet, builtInSnippets)
291         if (groupId == snippet.groupId())
292             insertSnippet(snippet);
293 }
294
295 void SnippetsCollection::reload()
296 {
297     clearSnippets();
298
299     const QList<Snippet> &builtInSnippets = allBuiltInSnippets();
300     QHash<QString, Snippet> activeBuiltInSnippets;
301     foreach (const Snippet &snippet, builtInSnippets)
302         activeBuiltInSnippets.insert(snippet.id(), snippet);
303
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);
310     }
311
312     foreach (const Snippet &snippet, activeBuiltInSnippets)
313         insertSnippet(snippet);
314 }
315
316 void SnippetsCollection::synchronize()
317 {
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 &current = snippet(i, groupId);
329                     if (!current.isBuiltIn() || current.isRemoved() || current.isModified())
330                         writeSnippetXML(current, &writer);
331                 }
332             }
333             writer.writeEndElement();
334             writer.writeEndDocument();
335             file.close();
336         }
337     }
338
339     reload();
340 }
341
342 void SnippetsCollection::writeSnippetXML(const Snippet &snippet, QXmlStreamWriter *writer) const
343 {
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();
353 }
354
355 QList<Snippet> SnippetsCollection::readXML(const QString &fileName, const QString &snippetId) const
356 {
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()));
374
375                             QString content;
376                             while (!xml.atEnd()) {
377                                 xml.readNext();
378                                 if (xml.isCharacters()) {
379                                     content += xml.text();
380                                 } else if (xml.isEndElement()) {
381                                     snippet.setContent(content);
382                                     snippets.append(snippet);
383                                     break;
384                                 }
385                             }
386
387                             if (!snippetId.isEmpty())
388                                 break;
389                         } else {
390                             xml.skipCurrentElement();
391                         }
392                     } else {
393                         xml.skipCurrentElement();
394                     }
395                 }
396             }
397         }
398         if (xml.hasError())
399             qWarning() << fileName << xml.errorString() << xml.lineNumber() << xml.columnNumber();
400         file.close();
401     }
402
403     return snippets;
404 }
405
406 QList<Snippet> SnippetsCollection::allBuiltInSnippets() const
407 {
408     QList<Snippet> builtInSnippets;
409     foreach (const QString &fileName, m_builtInSnippetsFiles)
410         builtInSnippets.append(readXML(fileName));
411     return builtInSnippets;
412 }
413
414 int SnippetsCollection::groupIndex(const QString &groupId) const
415 {
416     return m_groupIndexById.value(groupId);
417 }
418
419 void SnippetsCollection::identifyGroups()
420 {
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();
429     }
430
431     reload();
432 }
433
434 bool SnippetsCollection::isGroupKnown(const QString &groupId) const
435 {
436     return m_groupIndexById.value(groupId, -1) != -1;
437 }