OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / generichighlighter / manager.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 "manager.h"
35 #include "highlightdefinition.h"
36 #include "highlightdefinitionhandler.h"
37 #include "highlighterexception.h"
38 #include "definitiondownloader.h"
39 #include "highlightersettings.h"
40 #include "plaintexteditorfactory.h"
41 #include "texteditorconstants.h"
42 #include "texteditorplugin.h"
43 #include "texteditorsettings.h"
44
45 #include <coreplugin/icore.h>
46 #include <utils/qtcassert.h>
47 #include <coreplugin/progressmanager/progressmanager.h>
48 #include <qtconcurrent/QtConcurrentTools>
49
50 #include <QtCore/QtAlgorithms>
51 #include <QtCore/QtPlugin>
52 #include <QtCore/QString>
53 #include <QtCore/QLatin1Char>
54 #include <QtCore/QLatin1String>
55 #include <QtCore/QStringList>
56 #include <QtCore/QFile>
57 #include <QtCore/QFileInfo>
58 #include <QtCore/QDir>
59 #include <QtCore/QRegExp>
60 #include <QtCore/QFuture>
61 #include <QtCore/QtConcurrentRun>
62 #include <QtCore/QtConcurrentMap>
63 #include <QtCore/QUrl>
64 #include <QtCore/QSet>
65 #include <QtGui/QDesktopServices>
66 #include <QtGui/QMessageBox>
67 #include <QtXml/QXmlSimpleReader>
68 #include <QtXml/QXmlInputSource>
69 #include <QtXml/QXmlStreamReader>
70 #include <QtXml/QXmlStreamAttributes>
71 #include <QtNetwork/QNetworkRequest>
72 #include <QtNetwork/QNetworkReply>
73 #include <QtAlgorithms>
74
75 using namespace TextEditor;
76 using namespace Internal;
77
78 Manager::Manager() :
79     m_downloadingDefinitions(false),
80     m_registeringMimeTypes(false),
81     m_queuedMimeTypeRegistrations(0)
82 {
83     connect(&m_mimeTypeWatcher, SIGNAL(resultReadyAt(int)), this, SLOT(registerMimeType(int)));
84     connect(&m_mimeTypeWatcher, SIGNAL(finished()), this, SLOT(registerMimeTypesFinished()));
85     connect(&m_downloadWatcher, SIGNAL(finished()), this, SLOT(downloadDefinitionsFinished()));
86 }
87
88 Manager::~Manager()
89 {}
90
91 Manager *Manager::instance()
92 {
93     static Manager manager;
94     return &manager;
95 }
96
97 QString Manager::definitionIdByName(const QString &name) const
98 { return m_idByName.value(name); }
99
100 QString Manager::definitionIdByMimeType(const QString &mimeType) const
101 { return m_idByMimeType.value(mimeType); }
102
103 QString Manager::definitionIdByAnyMimeType(const QStringList &mimeTypes) const
104 {
105     QString definitionId;
106     foreach (const QString &mimeType, mimeTypes) {
107         definitionId = definitionIdByMimeType(mimeType);
108         if (!definitionId.isEmpty())
109             break;
110     }
111     return definitionId;
112 }
113
114 QSharedPointer<HighlightDefinition> Manager::definition(const QString &id)
115 {
116     if (!id.isEmpty() && !m_definitions.contains(id)) {
117         QFile definitionFile(id);
118         if (!definitionFile.open(QIODevice::ReadOnly | QIODevice::Text))
119             return QSharedPointer<HighlightDefinition>();
120
121         QSharedPointer<HighlightDefinition> definition(new HighlightDefinition);
122         HighlightDefinitionHandler handler(definition);
123
124         QXmlInputSource source(&definitionFile);
125         QXmlSimpleReader reader;
126         reader.setContentHandler(&handler);
127         m_isBuilding.insert(id);
128         try {
129             reader.parse(source);
130         } catch (HighlighterException &) {
131             definition.clear();
132         }
133         m_isBuilding.remove(id);
134         definitionFile.close();
135
136         m_definitions.insert(id, definition);
137     }
138
139     return m_definitions.value(id);
140 }
141
142 QSharedPointer<HighlightDefinitionMetaData> Manager::definitionMetaData(const QString &id) const
143 { return m_definitionsMetaData.value(id); }
144
145 bool Manager::isBuildingDefinition(const QString &id) const
146 { return m_isBuilding.contains(id); }
147
148 void Manager::registerMimeTypes()
149 {
150     if (!m_registeringMimeTypes) {
151         m_registeringMimeTypes = true;
152         clear();
153         QFuture<Core::MimeType> future =
154             QtConcurrent::run(&Manager::gatherDefinitionsMimeTypes, this);
155         m_mimeTypeWatcher.setFuture(future);
156         Core::ICore::instance()->progressManager()->addTask(future,
157                                                             tr("Registering definitions"),
158                                                             Constants::TASK_REGISTER_DEFINITIONS);
159     } else {
160         // QFutures returned from QConcurrent::run cannot be cancelled. So the queue.
161         ++m_queuedMimeTypeRegistrations;
162     }
163 }
164
165 void Manager::gatherDefinitionsMimeTypes(QFutureInterface<Core::MimeType> &future)
166 {
167     // Please be aware of the following limitation in the current implementation.
168     // The generic highlighter only register its types after all other plugins
169     // have populated Creator's MIME database (so it does not override anything).
170     // When the generic highlighter settings change only its internal data is cleaned-up
171     // and rebuilt. Creator's MIME database is not touched. So depending on how the
172     // user plays around with the generic highlighter file definitions (changing
173     // duplicated patterns, for example), some changes might not be reflected.
174     // A definitive implementation would require some kind of re-load or update
175     // (considering hierarchies, aliases, etc) of the MIME database whenever there
176     // is a change in the generic highlighter settings.
177
178     QStringList definitionsPaths;
179     const HighlighterSettings &settings = TextEditorSettings::instance()->highlighterSettings();
180     definitionsPaths.append(settings.definitionFilesPath());
181     if (settings.useFallbackLocation())
182         definitionsPaths.append(settings.fallbackDefinitionFilesPath());
183
184     Core::MimeDatabase *mimeDatabase = Core::ICore::instance()->mimeDatabase();
185     QSet<QString> knownSuffixes = QSet<QString>::fromList(mimeDatabase->suffixes());
186
187     foreach (const QString &path, definitionsPaths) {
188         if (path.isEmpty())
189             continue;
190
191         QDir definitionsDir(path);
192         QStringList filter(QLatin1String("*.xml"));
193         definitionsDir.setNameFilters(filter);
194
195         QList<QSharedPointer<HighlightDefinitionMetaData> > allMetaData;
196         const QFileInfoList &filesInfo = definitionsDir.entryInfoList();
197         foreach (const QFileInfo &fileInfo, filesInfo) {
198             const QSharedPointer<HighlightDefinitionMetaData> &metaData = parseMetadata(fileInfo);
199             if (!metaData.isNull())
200                 allMetaData.append(metaData);
201         }
202
203         // Consider definitions with higher priority first.
204         qSort(allMetaData.begin(), allMetaData.end(), PriorityComp());
205
206         foreach (const QSharedPointer<HighlightDefinitionMetaData> &metaData, allMetaData) {
207             if (m_idByName.contains(metaData->name()))
208                 // Name already exists... This is a fallback item, do not consider it.
209                 continue;
210
211             const QString &id = metaData->id();
212             m_idByName.insert(metaData->name(), id);
213             m_definitionsMetaData.insert(id, metaData);
214
215             static const QStringList textPlain(QLatin1String("text/plain"));
216
217             // A definition can specify multiple MIME types and file extensions/patterns.
218             // However, each thing is done with a single string. There is no direct way to
219             // tell which patterns belong to which MIME types nor whether a MIME type is just
220             // an alias for the other. Currently, I associate all patterns with all MIME
221             // types from a definition.
222             QList<Core::MimeGlobPattern> globPatterns;
223             foreach (const QString &type, metaData->mimeTypes()) {
224                 if (m_idByMimeType.contains(type))
225                     continue;
226
227                 m_idByMimeType.insert(type, id);
228                 Core::MimeType mimeType = mimeDatabase->findByType(type);
229                 if (mimeType.isNull()) {
230                     if (globPatterns.isEmpty()) {
231                         foreach (const QString &pattern, metaData->patterns()) {
232                             static const QLatin1String mark("*.");
233                             if (pattern.startsWith(mark)) {
234                                 const QString &suffix = pattern.right(pattern.length() - 2);
235                                 if (!knownSuffixes.contains(suffix))
236                                     knownSuffixes.insert(suffix);
237                                 else
238                                     continue;
239                             }
240                             QRegExp regExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard);
241                             globPatterns.append(Core::MimeGlobPattern(regExp, 50));
242                         }
243                     }
244
245                     mimeType.setType(type);
246                     mimeType.setSubClassesOf(textPlain);
247                     mimeType.setComment(metaData->name());
248                     mimeType.setGlobPatterns(globPatterns);
249
250                     mimeDatabase->addMimeType(mimeType);
251                     future.reportResult(mimeType);
252                 }
253             }
254         }
255     }
256 }
257
258 void Manager::registerMimeType(int index) const
259 {
260     const Core::MimeType &mimeType = m_mimeTypeWatcher.resultAt(index);
261     TextEditorPlugin::instance()->editorFactory()->addMimeType(mimeType.type());
262 }
263
264 void Manager::registerMimeTypesFinished()
265 {
266     m_registeringMimeTypes = false;
267     if (m_queuedMimeTypeRegistrations > 0) {
268         --m_queuedMimeTypeRegistrations;
269         registerMimeTypes();
270     } else {
271         emit mimeTypesRegistered();
272     }
273 }
274
275 QSharedPointer<HighlightDefinitionMetaData> Manager::parseMetadata(const QFileInfo &fileInfo)
276 {
277     static const QLatin1Char kSemiColon(';');
278     static const QLatin1String kLanguage("language");
279     static const QLatin1String kArtificial("text/x-artificial-");
280
281     QFile definitionFile(fileInfo.absoluteFilePath());
282     if (!definitionFile.open(QIODevice::ReadOnly | QIODevice::Text))
283         return QSharedPointer<HighlightDefinitionMetaData>();
284
285     QSharedPointer<HighlightDefinitionMetaData> metaData(new HighlightDefinitionMetaData);
286
287     QXmlStreamReader reader(&definitionFile);
288     while (!reader.atEnd() && !reader.hasError()) {
289         if (reader.readNext() == QXmlStreamReader::StartElement && reader.name() == kLanguage) {
290             const QXmlStreamAttributes &atts = reader.attributes();
291
292             metaData->setFileName(fileInfo.fileName());
293             metaData->setId(fileInfo.absoluteFilePath());
294             metaData->setName(atts.value(HighlightDefinitionMetaData::kName).toString());
295             metaData->setVersion(atts.value(HighlightDefinitionMetaData::kVersion).toString());
296             metaData->setPriority(atts.value(HighlightDefinitionMetaData::kPriority).toString()
297                                   .toInt());
298             metaData->setPatterns(atts.value(HighlightDefinitionMetaData::kExtensions)
299                                   .toString().split(kSemiColon, QString::SkipEmptyParts));
300
301             QStringList mimeTypes = atts.value(HighlightDefinitionMetaData::kMimeType).
302                                     toString().split(kSemiColon, QString::SkipEmptyParts);
303             if (mimeTypes.isEmpty()) {
304                 // There are definitions which do not specify a MIME type, but specify file
305                 // patterns. Creating an artificial MIME type is a workaround.
306                 QString artificialType(kArtificial);
307                 artificialType.append(metaData->name());
308                 mimeTypes.append(artificialType);
309             }
310             metaData->setMimeTypes(mimeTypes);
311
312             break;
313         }
314     }
315     reader.clear();
316     definitionFile.close();
317
318     return metaData;
319 }
320
321 QList<HighlightDefinitionMetaData> Manager::parseAvailableDefinitionsList(QIODevice *device) const
322 {
323     static const QLatin1Char kSlash('/');
324     static const QLatin1String kDefinition("Definition");
325
326     QList<HighlightDefinitionMetaData> metaDataList;
327     QXmlStreamReader reader(device);
328     while (!reader.atEnd() && !reader.hasError()) {
329         if (reader.readNext() == QXmlStreamReader::StartElement &&
330             reader.name() == kDefinition) {
331             const QXmlStreamAttributes &atts = reader.attributes();
332
333             HighlightDefinitionMetaData metaData;
334             metaData.setName(atts.value(HighlightDefinitionMetaData::kName).toString());
335             metaData.setVersion(atts.value(HighlightDefinitionMetaData::kVersion).toString());
336             QString url(atts.value(HighlightDefinitionMetaData::kUrl).toString());
337             metaData.setUrl(QUrl(url));
338             const int slash = url.lastIndexOf(kSlash);
339             if (slash != -1)
340                 metaData.setFileName(url.right(url.length() - slash - 1));
341
342             metaDataList.append(metaData);
343         }
344     }
345     reader.clear();
346
347     return metaDataList;
348 }
349
350 void Manager::downloadAvailableDefinitionsMetaData()
351 {
352     QUrl url(QLatin1String("http://www.kate-editor.org/syntax/update-3.2.xml"));
353     QNetworkRequest request(url);
354     // Currently this takes a couple of seconds on Windows 7: QTBUG-10106.
355     QNetworkReply *reply = m_networkManager.get(request);
356     connect(reply, SIGNAL(finished()), this, SLOT(downloadAvailableDefinitionsListFinished()));
357 }
358
359 void Manager::downloadAvailableDefinitionsListFinished()
360 {
361     if (QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender())) {
362         if (reply->error() == QNetworkReply::NoError)
363             emit definitionsMetaDataReady(parseAvailableDefinitionsList(reply));
364         else
365             emit errorDownloadingDefinitionsMetaData();
366         reply->deleteLater();
367     }
368 }
369
370 void Manager::downloadDefinitions(const QList<QUrl> &urls, const QString &savePath)
371 {
372     m_downloaders.clear();
373     foreach (const QUrl &url, urls)
374         m_downloaders.append(new DefinitionDownloader(url, savePath));
375
376     m_downloadingDefinitions = true;
377     QFuture<void> future = QtConcurrent::map(m_downloaders, DownloaderStarter());
378     m_downloadWatcher.setFuture(future);
379     Core::ICore::instance()->progressManager()->addTask(future,
380                                                         tr("Downloading definitions"),
381                                                         Constants::TASK_DOWNLOAD_DEFINITIONS);
382 }
383
384 void Manager::downloadDefinitionsFinished()
385 {
386     int errors = 0;
387     bool writeError = false;
388     foreach (DefinitionDownloader *downloader, m_downloaders) {
389         DefinitionDownloader::Status status = downloader->status();
390         if (status != DefinitionDownloader::Ok) {
391             ++errors;
392             if (status == DefinitionDownloader::WriteError && !writeError)
393                 writeError = true;
394         }
395         delete downloader;
396     }
397
398     if (errors > 0) {
399         QString text;
400         if (errors == m_downloaders.size())
401             text = tr("Error downloading selected definition(s).");
402         else
403             text = tr("Error downloading one or more definitions.");
404         if (writeError)
405             text.append(tr("\nPlease check the directory's access rights."));
406         QMessageBox::critical(0, tr("Download Error"), text);
407     }
408
409     m_downloadingDefinitions = false;
410 }
411
412 bool Manager::isDownloadingDefinitions() const
413 {
414     return m_downloadingDefinitions;
415 }
416
417 void Manager::clear()
418 {
419     m_idByName.clear();
420     m_idByMimeType.clear();
421     m_definitions.clear();
422     m_definitionsMetaData.clear();
423 }