diff --git a/interfaces/iassistant.cpp b/interfaces/iassistant.cpp index 2ff26ff086..1c3830c09d 100644 --- a/interfaces/iassistant.cpp +++ b/interfaces/iassistant.cpp @@ -1,95 +1,102 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iassistant.h" #include #include +#include using namespace KDevelop; Q_DECLARE_METATYPE(KSharedPtr) +// Very slow and ugly, but very secure +static QString removeHtmlFromString(QString string) +{ + return QTextEdit(string).toPlainText(); +} + KAction* KDevelop::IAssistantAction::toKAction() const { - KAction* ret = new KAction(KIcon(icon()), description(), 0); + KAction* ret = new KAction(KIcon(icon()), removeHtmlFromString(description()), 0); ret->setToolTip(toolTip()); qRegisterMetaType >("KSharedPtr()"); //Add the data as a KSharedPtr to the action, so this assistant stays alive at least as long as the KAction ret->setData(QVariant::fromValue(KSharedPtr(const_cast(this)))); connect(ret, SIGNAL(triggered(bool)), SLOT(execute()), Qt::QueuedConnection); return ret; } KDevelop::IAssistant::~IAssistant() { } KDevelop::IAssistantAction::IAssistantAction() : KSharedObject(*(QObject*)this) { } KDevelop::IAssistantAction::~IAssistantAction() { } QIcon KDevelop::IAssistantAction::icon() const { return QIcon(); } QString KDevelop::IAssistantAction::toolTip() const { return QString(); } KDevelop::IAssistant::IAssistant() : KSharedObject(*(QObject*)this) { } QIcon KDevelop::IAssistant::icon() const { return QIcon(); } QString KDevelop::IAssistant::title() const { return QString(); } void KDevelop::IAssistant::doHide() { emit hide(); } QList< KDevelop::IAssistantAction::Ptr > KDevelop::IAssistant::actions() const { return m_actions; } void KDevelop::IAssistant::addAction(KDevelop::IAssistantAction::Ptr action) { m_actions << action; } void KDevelop::IAssistant::clearActions() { m_actions.clear(); } QString KDevelop::DummyAssistantAction::description() const { return m_description; } KDevelop::DummyAssistantAction::DummyAssistantAction(QString desc) : m_description(desc) { } void KDevelop::DummyAssistantAction::execute() { } #include "iassistant.moc" diff --git a/interfaces/iassistant.h b/interfaces/iassistant.h index 9939376c5c..d3c9b30947 100644 --- a/interfaces/iassistant.h +++ b/interfaces/iassistant.h @@ -1,113 +1,115 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVELOP_IASSISTANT_H #define KDEVELOP_IASSISTANT_H #include #include #include #include "interfacesexport.h" #include class KAction; namespace KDevelop { ///Represents a single assistant action. ///Subclass it to create your own actions. class KDEVPLATFORMINTERFACES_EXPORT IAssistantAction : public QObject, public KSharedObject { Q_OBJECT public: IAssistantAction(); typedef KSharedPtr Ptr; virtual ~IAssistantAction(); ///Creates a KAction that represents this exact assistant action. ///The caller owns the action, and is responsible for deleting it. virtual KAction* toKAction() const; ///Should return a short description of the action. ///It may contain simple HTML formatting. ///Must be very short, so it nicely fits into the assistant popups. virtual QString description() const = 0; ///May return additional tooltip hover information. ///The default implementation returns an empty string. virtual QString toolTip() const; ///May return an icon for this action. ///The default implementation returns an invalid icon, which means that no icon is shown. virtual QIcon icon() const; public Q_SLOTS: ///Execute this action. virtual void execute() = 0; }; ///For testing purposes: This action just shows the given string, and does nothing on execution. class KDEVPLATFORMINTERFACES_EXPORT DummyAssistantAction : public IAssistantAction { public: DummyAssistantAction(QString desc); virtual void execute(); virtual QString description() const; private: QString m_description; }; ///Represents a single assistant popup. ///Subclass it to create your own assistants. class KDEVPLATFORMINTERFACES_EXPORT IAssistant : public QObject, public KSharedObject { Q_OBJECT public: IAssistant(); virtual ~IAssistant(); typedef KSharedPtr Ptr; ///Returns the stored list of actions, or can be overridden to return an own set. virtual QList actions() const; ///Adds the given action to the list of actions. ///Does not emit actionsChanged(), you have to do that when you're ready. virtual void addAction(IAssistantAction::Ptr action); ///Clears the stored list of actions. ///Does not emit actionsChanged(), you have to do that when you're ready. virtual void clearActions(); ///May return an icon for this assistant virtual QIcon icon() const; ///May return the title of this assistant + ///The text may be html formatted. If it can be confused with HTML, + ///use Qt::escape(..). virtual QString title() const; public Q_SLOTS: ///Emits hide(), which causes this assistant to be hidden virtual void doHide(); Q_SIGNALS: ///Can be emitted by the assistant when it should be hidden void hide(); ///Can be emitted by the assistant when its actions have changed and should be re-read void actionsChanged(); private: QList m_actions; }; } #endif // KDEVELOP_IASSISTANT_H diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index 27e1e9f1a6..28bdfeef56 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,704 +1,699 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "parsejob.h" #include "parserdependencypolicy.h" #include #include #include #include #include const bool separateThreadForHighPriority = true; namespace KDevelop { class BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_delay = 500; m_threads = 1; m_doneParseJobs = 0; m_maxParseJobs = 0; m_neededPriority = BackgroundParser::WorstPriority; ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, SIGNAL(timeout()), m_parser, SLOT(parseDocuments())); } void startTimerThreadSafe() { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection); } ~BackgroundParserPrivate() { suspend(); m_weaver.dequeue(); m_weaver.requestAbort(); m_weaver.finish(); // Release dequeued jobs QHashIterator it = m_parseJobs; while (it.hasNext()) { it.next(); - it.value()->setBackgroundParser(0); delete it.value(); } } // Non-mutex guarded functions, only call with m_mutex acquired. void parseDocumentsInternal() { if(m_shuttingDown) return; // Create delayed jobs, that is, jobs for documents which have been changed // by the user. QList jobs; for (QMap >::Iterator it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { if(it1.key() > m_neededPriority) break; //The priority is not good enough to be processed right now for(QSet::Iterator it = it1.value().begin(); it != it1.value().end();) { //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if(m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority) ) break; if(m_parseJobs.count() >= m_threads && it1.key() > BackgroundParser::NormalPriority && !specialParseJob) break; //The additional parsing thread is reserved for higher priority parsing // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(*it) ) { ++it; continue; } kDebug(9505) << "creating parse-job" << *it << "new count of active parse-jobs:" << m_parseJobs.count() + 1; ParseJob* job = createParseJob(*it, m_documents[*it].features(), m_documents[*it].notifyWhenReady()); if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = job; //This parse-job is allocated into the reserved thread if(job) jobs.append(job); m_documents.remove(*it); it = it1.value().erase(it); --m_maxParseJobs; //We have added one when putting the document into m_documents if(!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. // If there are more documents to parse, instantly re-try. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); break; } } } // Ok, enqueueing is fine because m_parseJobs contains all of the jobs now foreach (ParseJob* job, jobs) m_weaver.enqueue(job); m_parser->updateProgressBar(); //We don't hide the progress-bar in updateProgressBar, so it doesn't permanently flash when a document is reparsed again and again. if(m_doneParseJobs == m_maxParseJobs || (m_neededPriority == BackgroundParser::BestPriority && m_weaver.queueLength() == 0)) { emit m_parser->hideProgress(m_parser); } } ParseJob* createParseJob(const KUrl& url, TopDUContext::Features features, QList > notifyWhenReady) { QList languages = m_languageController->languagesForUrl(url); foreach (ILanguage* language, languages) { if(!language) { kWarning() << "got zero language for" << url; continue; } if(!language->languageSupport()) { kWarning() << "language has no language support assigned:" << language->name(); continue; } ParseJob* job = language->languageSupport()->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setMinimumFeatures(features); - job->setBackgroundParser(m_parser); job->setNotifyWhenReady(notifyWhenReady); - job->setTracker(m_parser->trackerForUrl(IndexedString(url))); QObject::connect(job, SIGNAL(done(ThreadWeaver::Job*)), m_parser, SLOT(parseComplete(ThreadWeaver::Job*))); QObject::connect(job, SIGNAL(failed(ThreadWeaver::Job*)), m_parser, SLOT(parseComplete(ThreadWeaver::Job*))); QObject::connect(job, SIGNAL(progress(KDevelop::ParseJob*, float, QString)), m_parser, SLOT(parseProgress(KDevelop::ParseJob*, float, QString)), Qt::QueuedConnection); m_parseJobs.insert(url, job); ++m_maxParseJobs; // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return job; } if(languages.isEmpty()) kDebug() << "found no languages for url" << url; else kDebug() << "could not create parse-job for url" << url; //Notify that we failed typedef QWeakPointer Notify; foreach(const Notify& n, notifyWhenReady) if(n) QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, IndexedString(url)), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); return 0; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KGlobal::config(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", 1)); resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { bool s = m_weaver.state().stateId() == ThreadWeaver::Suspended || m_weaver.state().stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state().stateId() == ThreadWeaver::Suspended || m_weaver.state().stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QWeakPointer specialParseJob; QTimer m_timer; int m_delay; int m_threads; bool m_shuttingDown; struct DocumentParseTarget { QWeakPointer notifyWhenReady; int priority; TopDUContext::Features features; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; struct DocumentParsePlan { QList targets; int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) if(target.priority < ret) ret = target.priority; return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) ret = (TopDUContext::Features) (ret | target.features); return ret; } QList > notifyWhenReady() { QList > ret; foreach(const DocumentParseTarget &target, targets) if(target.notifyWhenReady) ret << target.notifyWhenReady; return ret; } }; // A list of documents that are planned to be parsed, and their priority QMap m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // A change tracker for each managed document QHash m_managed; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; ThreadWeaver::Weaver m_weaver; ParserDependencyPolicy m_dependencyPolicy; QMutex m_mutex; int m_maxParseJobs; int m_doneParseJobs; QMap m_jobProgress; int m_neededPriority; //The minimum priority needed for processed jobs }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), SIGNAL(documentLoaded(KDevelop::IDocument*)), this, SLOT(documentLoaded(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(documentUrlChanged(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*))); connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::clear(QObject* parent) { QMutexLocker lock(&d->m_mutex); QHashIterator it = d->m_parseJobs; while (it.hasNext()) { it.next(); if (it.value()->parent() == parent) { it.value()->requestAbort(); } } } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressBar(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QWeakPointer p(notifyWhenReady); QMutexLocker lock(&d->m_mutex); for(QMap::iterator it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); int index = -1; for(int a = 0; a < (*it).targets.size(); ++a) if((*it).targets[a].notifyWhenReady.data() == notifyWhenReady) index = a; if(index != -1) { (*it).targets.removeAt(index); if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const KUrl& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady) { // kDebug(9505) << "BackgroundParser::addDocument" << url.prettyUrl(); QMutexLocker lock(&d->m_mutex); { Q_ASSERT(url.isValid()); BackgroundParserPrivate::DocumentParseTarget target; target.priority = priority; target.features = features; target.notifyWhenReady = QWeakPointer(notifyWhenReady); QMap::iterator it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // kDebug(9505) << "BackgroundParser::addDocument: queuing" << url; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } d->startTimerThreadSafe(); } } void BackgroundParser::addDocumentList(const KUrl::List &urls, TopDUContext::Features features, int priority) { foreach (const KUrl &url, urls) addDocument(url, features, priority); } void BackgroundParser::removeDocument(const KUrl &url, QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); Q_ASSERT(url.isValid()); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(BackgroundParserPrivate::DocumentParseTarget target, d->m_documents[url].targets) if(target.notifyWhenReady.data() == notifyWhenReady) d->m_documents[url].targets.removeAll(target); if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(ThreadWeaver::Job* job) { if (ParseJob* parseJob = qobject_cast(job)) { emit parseJobFinished(parseJob); { { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document().str()); d->m_jobProgress.remove(parseJob); - parseJob->setBackgroundParser(0); - ++d->m_doneParseJobs; updateProgressBar(); } //Unlock the mutex before deleting the parse-job, because the parse-job //has a virtual destructor that may lock the duchain, leading to deadlocks delete parseJob; } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } bool BackgroundParser::isQueued(KUrl url) const { QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressBar(); } void BackgroundParser::updateProgressBar() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { kDebug() << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for(QMap::const_iterator it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) additionalProgress += *it; emit showProgress(this, 0, d->m_maxParseJobs*1000, (additionalProgress + d->m_doneParseJobs)*1000); } } ParserDependencyPolicy* BackgroundParser::dependencyPolicy() const { return &d->m_dependencyPolicy; } ParseJob* BackgroundParser::parseJobForDocument(const KUrl& document) const { QMutexLocker lock(&d->m_mutex); if (d->m_parseJobs.contains(document)) { return d->m_parseJobs[document]; } return 0; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } void BackgroundParser::setDelay(int miliseconds) { if (d->m_delay != miliseconds) { d->m_delay = miliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_mutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { QMutexLocker l(&d->m_mutex); QHash< IndexedString, DocumentChangeTracker* >::iterator it = d->m_managed.find(url); if(it != d->m_managed.end()) return *it; else return 0; } void BackgroundParser::documentClosed ( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); Q_ASSERT(d->m_managed.contains(url)); kDebug() << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { kDebug() << "Got redundant documentLoaded from" << document->url() << textDocument; return; } kDebug() << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ kDebug() << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !d->m_managed.contains(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer() { d->m_timer.start(d->m_delay); } } #include "backgroundparser.moc" diff --git a/language/backgroundparser/parsejob.cpp b/language/backgroundparser/parsejob.cpp index 465cd465fa..230968f38c 100644 --- a/language/backgroundparser/parsejob.cpp +++ b/language/backgroundparser/parsejob.cpp @@ -1,464 +1,417 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundparser.h" #include "parserdependencypolicy.h" #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include #include #include #include #include #include using namespace KTextEditor; Q_DECLARE_METATYPE(KDevelop::IndexedString) Q_DECLARE_METATYPE(KDevelop::IndexedTopDUContext) Q_DECLARE_METATYPE(KDevelop::ReferencedTopDUContext) static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const KUrl& url) : document( IndexedString(url.pathOrUrl()) ) , backgroundParser( 0 ) , abortMutex(new QMutex) , hasReadContents( false ) , abortRequested( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) { } ~ParseJobPrivate() { delete abortMutex; } ReferencedTopDUContext duContext; KDevelop::IndexedString document; QString errorMessage; BackgroundParser* backgroundParser; QMutex* abortMutex; ParseJob::Contents contents; bool hasReadContents : 1; volatile bool abortRequested : 1; bool aborted : 1; TopDUContext::Features features; QList > notify; QWeakPointer tracker; RevisionReference revision; RevisionReference previousRevision; }; ParseJob::ParseJob( const KUrl &url ) : ThreadWeaver::JobSequence(), d(new ParseJobPrivate(url)) {} -void ParseJob::setTracker ( DocumentChangeTracker* tracker ) -{ - d->tracker = tracker; -} - -DocumentChangeTracker* ParseJob::tracker() const -{ - return d->tracker.data(); -} - ParseJob::~ParseJob() { typedef QWeakPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) if(p) QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->document), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); delete d; } IndexedString ParseJob::document() const { return d->document; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); return ::staticMinimumFeatures.size(); } TopDUContext::Features ParseJob::staticMinimumFeatures(IndexedString url) { QMutexLocker lock(&minimumFeaturesMutex); TopDUContext::Features features = (TopDUContext::Features)0; if(::staticMinimumFeatures.contains(url)) foreach(const TopDUContext::Features &f, ::staticMinimumFeatures[url]) features = (TopDUContext::Features)(features | f); return features; } TopDUContext::Features ParseJob::minimumFeatures() const { return (TopDUContext::Features)(d->features | staticMinimumFeatures(d->document)); } void ParseJob::setDuChain(ReferencedTopDUContext duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } -void ParseJob::addJob(Job* job) -{ - if (backgroundParser()) - job->assignQueuePolicy(backgroundParser()->dependencyPolicy()); - - JobSequence::addJob(job); -} - -BackgroundParser* ParseJob::backgroundParser() const -{ - return d->backgroundParser; -} - -void ParseJob::setBackgroundParser(BackgroundParser* parser) -{ - if (parser) { - assignQueuePolicy(parser->dependencyPolicy()); - - for (int i = 0; i < jobListLength(); ++i) - jobAt(i)->assignQueuePolicy(parser->dependencyPolicy()); - - } else if (d->backgroundParser) { - - removeQueuePolicy(d->backgroundParser->dependencyPolicy()); - - for (int i = 0; i < jobListLength(); ++i) - jobAt(i)->removeQueuePolicy(d->backgroundParser->dependencyPolicy()); - } - - d->backgroundParser = parser; -} - -bool ParseJob::addDependency(ParseJob* dependency, ThreadWeaver::Job* actualDependee) -{ - if (!backgroundParser()) - return false; - - return backgroundParser()->dependencyPolicy()->addDependency(dependency, this, actualDependee); -} - bool ParseJob::abortRequested() const { QMutexLocker lock(d->abortMutex); return d->abortRequested; } void ParseJob::requestAbort() { QMutexLocker lock(d->abortMutex); d->abortRequested = true; } void ParseJob::abortJob() { d->aborted = true; setFinished(true); } void ParseJob::setNotifyWhenReady(QList< QWeakPointer< QObject > > notify ) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(IndexedString url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(IndexedString url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].removeOne(features); if(::staticMinimumFeatures[url].isEmpty()) ::staticMinimumFeatures.remove(url); } KDevelop::ProblemPointer ParseJob::readContents() { Q_ASSERT(!d->hasReadContents); d->hasReadContents = true; QString localFile(document().toUrl().toLocalFile()); QFileInfo fileInfo( localFile ); QDateTime lastModified = fileInfo.lastModified(); ForegroundLock lock; + + d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); //Try using an artificial code-representation, which overrides everything else if(artificialCodeRepresentationExists(document())) { CodeRepresentation::Ptr repr = createCodeRepresentation(document()); d->contents.contents = repr->text().toUtf8(); kDebug() << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } if(DocumentChangeTracker* t = d->tracker.data()) { // The file is open in an editor d->previousRevision = t->revisionAtLastReset(); t->reset(); // Reset the tracker to the current revision Q_ASSERT(t->revisionAtLastReset()); d->contents.contents = t->textAtLastReset().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); }else{ // We have to load the file from disk lock.unlock(); // Unlock the foreground lock before reading from disk, so the UI won't block due to I/O QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(KDevelop::ProblemData::Disk); p->setDescription(i18n( "Could not open file '%1'", localFile )); switch (file.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from disk.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File could not be read from disk due to permissions.")); break; default: break; } p->setFinalLocation(DocumentRange(document(), SimpleRange::invalid())); kWarning( 9007 ) << "Could not open file" << document().str() << "(path" << localFile << ")" ; return p; } d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } + // To make the parsing more robust, we add some zeroes to the end of the buffer. d->contents.contents.push_back((char)0); d->contents.contents.push_back((char)0); d->contents.contents.push_back((char)0); d->contents.contents.push_back((char)0); return KDevelop::ProblemPointer(); } const KDevelop::ParseJob::Contents& ParseJob::contents() const { Q_ASSERT(d->hasReadContents); return d->contents; } struct MovingRangeTranslator : public DUChainVisitor { MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source), target(_target), moving(_moving) { } virtual void visit(DUContext* context) { translateRange(context); ///@todo Also map import-positions // Translate uses uint usesCount = context->usesCount(); for(uint u = 0; u < usesCount; ++u) { RangeInRevision r = context->uses()[u].m_range; translateRange(r); context->changeUseRange(u, r); } } virtual void visit(Declaration* declaration) { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target); } KTextEditor::Range range; qint64 source; qint64 target; MovingInterface* moving; }; void ParseJob::translateDUChainToRevision(TopDUContext* context) { qint64 targetRevision = d->contents.modification.revision; if(targetRevision == -1) { kDebug() << "invalid target revision" << targetRevision; return; } qint64 sourceRevision; { DUChainReadLocker duChainLock; Q_ASSERT(context->parsingEnvironmentFile()); // Cannot map if there is no source revision sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision; if(sourceRevision == -1) { kDebug() << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { kDebug() << "for document" << document().str() << ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision; return; } ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { if(!d->previousRevision) { kDebug() << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { kDebug() << "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)"; return; } if(!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) { kDebug() << "lost one of the translation revisions, not doing the map"; return; } // Perform translation MovingInterface* moving = t->documentMovingInterface(); DUChainWriteLocker wLock; MovingRangeTranslator translator(sourceRevision, targetRevision, moving); context->visit(translator); QList< ProblemPointer > problems = context->problems(); for(QList< ProblemPointer >::iterator problem = problems.begin(); problem != problems.end(); ++problem) { RangeInRevision r = (*problem)->range(); translator.translateRange(r); (*problem)->setRange(r); } // Update the modification revision in the meta-data ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision(); modRev.revision = targetRevision; context->parsingEnvironmentFile()->setModificationRevision(modRev); } } } #include "parsejob.moc" diff --git a/language/backgroundparser/parsejob.h b/language/backgroundparser/parsejob.h index c13afd606f..135319c2b8 100644 --- a/language/backgroundparser/parsejob.h +++ b/language/backgroundparser/parsejob.h @@ -1,174 +1,149 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PARSEJOB_H #define PARSEJOB_H #include #include #include #include "../duchain/indexedstring.h" -#include "documentchangetracker.h" #include #include namespace KDevelop { class BackgroundParser; class TopDUContext; class ReferencedTopDUContext; /** * The base class for background parser jobs. * * In your language plugin, don't forget to use acquire an UrlParseLock before starting to the actual parsing. */ class KDEVPLATFORMLANGUAGE_EXPORT ParseJob : public ThreadWeaver::JobSequence { Q_OBJECT public: ParseJob( const KUrl &url ); /** * _No_ mutexes/locks are allowed to be locked when this object is destroyed (except for optionally the foreground lock) * */ virtual ~ParseJob(); - Q_SCRIPTABLE BackgroundParser* backgroundParser() const; - Q_SCRIPTABLE void setBackgroundParser(BackgroundParser* parser); - struct Contents { // Modification-time of the read content ModificationRevision modification; // The contents in utf-8 format QByteArray contents; }; /** * _No_ mutexes/locks are allowed to be locked when this is called (except for optionally the foreground lock) * * Locks the document revision so that mapping from/to the revision in the editor using MovingInterface will be possible. * * Returns an invalid pointer if the call succeeds, and a valid one if the reading fails. * */ KDevelop::ProblemPointer readContents(); /** * After reading the contents, you can call this to retrieve it. * */ const Contents& contents() const; /** * Translates the given context from its previous revision to the revision that has * been retrieved during readContents(). The top-context meta-data will be updated * with the revision. * * This can be done after reading the context before updating, so * that the correct ranges are matched onto each other during the update. * * _No_ mutexes/locks are allowed to be locked when this is called (except for optionally the foreground lock) */ void translateDUChainToRevision(TopDUContext* context); - /** - * Assigns a document change tracker to this job. - * */ - void setTracker(DocumentChangeTracker* tracker); - - /** - * Returns the document change tracker for this job. May be zero if the document is not open in an editor. - * */ - DocumentChangeTracker* tracker() const; - /// \returns the indexed url of the document to be parsed. Q_SCRIPTABLE KDevelop::IndexedString document() const; /** * Sets a list of QObjects that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once the parse-job finishes, from within its destructor. * The given top-context may be invalid if the update failed. */ Q_SCRIPTABLE void setNotifyWhenReady(QList > notify); /// Sets the du-context that was created by this parse-job Q_SCRIPTABLE virtual void setDuChain(ReferencedTopDUContext duChain); /// Returns the set du-context, or zero of none was set. Q_SCRIPTABLE virtual ReferencedTopDUContext duChain() const; /// Overridden to allow jobs to determine if they've been requested to abort Q_SCRIPTABLE virtual void requestAbort(); /// Determine if the job has been requested to abort Q_SCRIPTABLE bool abortRequested() const; /// Sets success to false, causing failed() to be emitted Q_SCRIPTABLE void abortJob(); /// Overridden to convey whether the job succeeded or not. Q_SCRIPTABLE virtual bool success() const; - /// Overridden to set the DependencyPolicy on subjobs. - Q_SCRIPTABLE virtual void addJob(Job* job); - /// Set the minimum features the resulting top-context should have Q_SCRIPTABLE void setMinimumFeatures(TopDUContext::Features features); /// Minimum set of features the resulting top-context should have Q_SCRIPTABLE TopDUContext::Features minimumFeatures() const; /// Allows statically specifying an amount of features required for an url. /// These features will automatically be or'ed with the minimumFeatures() returned /// by any ParseJob with the given url. /// Since this causes some additional complixity in update-checking, minimum features should not /// be set permanently. static void setStaticMinimumFeatures(IndexedString url, TopDUContext::Features features); /// Must be called exactly once for each call to setStaticMinimumFeatures, with the same features. static void unsetStaticMinimumFeatures(IndexedString url, TopDUContext::Features features); /// Returns the statically set minimum features for the given url, or zero. static TopDUContext::Features staticMinimumFeatures(IndexedString url); /// Returns whether there is minimum features set up for some url static bool hasStaticMinimumFeatures(); - - /** - * Attempt to add \a dependency as a dependency of \a actualDependee, which must - * be a subjob of this job, or null (in which case, the dependency is added - * to this job). If a circular dependency is detected, the dependency will - * not be added and the method will return false. - */ - Q_SCRIPTABLE bool addDependency(ParseJob* dependency, ThreadWeaver::Job* actualDependee = 0); Q_SIGNALS: /**Can be used to give progress feedback to the background-parser. @param value should be between 0 and 1, where 0 = 0% and 1 = 100% * @param text may be a text that describes the current state of parsing * Do not trigger this too often, for performance reasons. */ void progress(KDevelop::ParseJob*, float value, QString text); private: class ParseJobPrivate* const d; }; } #endif diff --git a/language/codegen/documentchangeset.cpp b/language/codegen/documentchangeset.cpp index a2b93051f9..0c8efd2b1d 100644 --- a/language/codegen/documentchangeset.cpp +++ b/language/codegen/documentchangeset.cpp @@ -1,408 +1,419 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentchangeset.h" #include "coderepresentation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { struct DocumentChangeSetPrivate { DocumentChangeSet::ReplacementPolicy replacePolicy; DocumentChangeSet::FormatPolicy formatPolicy; DocumentChangeSet::DUChainUpdateHandling updatePolicy; DocumentChangeSet::ActivationPolicy activationPolicy; QMap< IndexedString, QList > changes; DocumentChangeSet::ChangeResult addChange(DocumentChangePointer change); DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation * repr, const QString & newText, const QList & sortedChangesList); DocumentChangeSet::ChangeResult generateNewText(const KDevelop::IndexedString & file, QList< KDevelop::DocumentChangePointer > & sortedChanges, const KDevelop::CodeRepresentation* repr, QString& output); DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString & file, QList & filteredChanges); void formatChanges(); void updateFiles(); }; //Simple helper to clear up code clutter namespace { inline bool changeIsValid(const DocumentChange & change, const QStringList & textLines) { return change.m_range.start <= change.m_range.end && change.m_range.end.line < textLines.size() && change.m_range.start.line >= 0 && change.m_range.start.column >= 0 && change.m_range.start.column <= textLines[change.m_range.start.line].length() && change.m_range.end.column >= 0 && change.m_range.end.column <= textLines[change.m_range.end.line].length() && change.m_range.start.line == change.m_range.end.line; } inline bool duplicateChanges(DocumentChangePointer previous, DocumentChangePointer current) { //Given the option of considering a duplicate two changes in the same range but with different old texts to be ignored return previous->m_range == current->m_range && previous->m_newText == current->m_newText && (previous->m_oldText == current->m_oldText || (previous->m_ignoreOldText && current->m_ignoreOldText)); } } DocumentChangeSet::DocumentChangeSet() : d(new DocumentChangeSetPrivate) { d->replacePolicy = StopOnFailedChange; d->formatPolicy = AutoFormatChanges; d->updatePolicy = SimpleUpdate; d->activationPolicy = DoNotActivate; } DocumentChangeSet::DocumentChangeSet(const DocumentChangeSet & rhs) : d(new DocumentChangeSetPrivate(*rhs.d)) { } DocumentChangeSet& DocumentChangeSet::operator=(const KDevelop::DocumentChangeSet& rhs) { *d = *rhs.d; return *this; } DocumentChangeSet::~DocumentChangeSet() { delete d; } KDevelop::DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const KDevelop::DocumentChange& change) { return d->addChange(DocumentChangePointer(new DocumentChange(change))); } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(DocumentChangePointer change) { return d->addChange(change); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(DocumentChangePointer change) { if(change->m_range.start.line != change->m_range.end.line) return DocumentChangeSet::ChangeResult("Multi-line ranges are not supported"); changes[change->m_document].append(change); return true; } void DocumentChangeSet::setReplacementPolicy ( DocumentChangeSet::ReplacementPolicy policy ) { d->replacePolicy = policy; } void DocumentChangeSet::setFormatPolicy ( DocumentChangeSet::FormatPolicy policy ) { d->formatPolicy = policy; } void DocumentChangeSet::setUpdateHandling ( DocumentChangeSet::DUChainUpdateHandling policy ) { d->updatePolicy = policy; } void DocumentChangeSet::setActivationPolicy(DocumentChangeSet::ActivationPolicy policy) { d->activationPolicy = policy; } QMap< IndexedString, InsertArtificialCodeRepresentationPointer > DocumentChangeSet::temporaryCodeRepresentations() const { QMap< IndexedString, InsertArtificialCodeRepresentationPointer > ret; ChangeResult result(true); foreach(const IndexedString &file, d->changes.keys()) { CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr) continue; QList sortedChangesList; result = d->removeDuplicates(file, sortedChangesList); if(!result) continue; QString newText; result = d->generateNewText(file, sortedChangesList, repr.data(), newText); if(!result) continue; InsertArtificialCodeRepresentationPointer code( new InsertArtificialCodeRepresentation(IndexedString(file.toUrl().fileName()), newText) ); ret.insert(file, code); } return ret; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges() { QMap codeRepresentations; QMap newTexts; QMap > filteredSortedChanges; ChangeResult result(true); QList files(d->changes.keys()); foreach(const IndexedString &file, files) { CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr) return ChangeResult(QString("Could not create a Representation for %1").arg(file.str())); codeRepresentations[file] = repr; QList& sortedChangesList(filteredSortedChanges[file]); { result = d->removeDuplicates(file, sortedChangesList); if(!result) return result; } { result = d->generateNewText(file, sortedChangesList, repr.data(), newTexts[file]); if(!result) return result; } } QMap oldTexts; //Apply the changes to the files foreach(const IndexedString &file, files) { oldTexts[file] = codeRepresentations[file]->text(); result = d->replaceOldText(codeRepresentations[file].data(), newTexts[file], filteredSortedChanges[file]); if(!result && d->replacePolicy == StopOnFailedChange) { //Revert all files foreach(const IndexedString &revertFile, oldTexts.keys()) codeRepresentations[revertFile]->setText(oldTexts[revertFile]); return result; } } - ModificationRevisionSet::clearCache(); - d->updateFiles(); if(d->activationPolicy == Activate) foreach(const IndexedString& file, files) ICore::self()->documentController()->openDocument(file.toUrl()); return result; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation * repr, const QString & newText, const QList & sortedChangesList) { DynamicCodeRepresentation* dynamic = dynamic_cast(repr); if(dynamic) { dynamic->startEdit(); //Replay the changes one by one for(int pos = sortedChangesList.size()-1; pos >= 0; --pos) { const DocumentChange& change(*sortedChangesList[pos]); if(!dynamic->replace(change.m_range.textRange(), change.m_oldText, change.m_newText, change.m_ignoreOldText)) { QString warningString = QString("Inconsistent change in %1 at %2:%3 -> %4:%5 = %6(encountered \"%7\") -> \"%8\"") .arg(change.m_document.str()).arg(change.m_range.start.line).arg(change.m_range.start.column) .arg(change.m_range.end.line).arg(change.m_range.end.column).arg(change.m_oldText) .arg(dynamic->rangeText(change.m_range.textRange())).arg(change.m_newText); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { kWarning() << warningString; } else if(replacePolicy == DocumentChangeSet::StopOnFailedChange) { dynamic->endEdit(); return DocumentChangeSet::ChangeResult(warningString); } //If set to ignore failed changes just continue with the others } } dynamic->endEdit(); return true; } //For files on disk if (!repr->setText(newText)) { QString warningString = QString("Could not replace text in the document: %1").arg(sortedChangesList.begin()->data()->m_document.str()); if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) { kWarning() << warningString; } return DocumentChangeSet::ChangeResult(warningString); } return true; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString & file, QList & sortedChanges, const CodeRepresentation * repr, QString & output) { ISourceFormatter* formatter = 0; if(ICore::self()) formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl()); //Create the actual new modified file QStringList textLines = repr->text().split('\n'); KMimeType::Ptr mime = KMimeType::findByUrl(file.toUrl()); for(int pos = sortedChanges.size()-1; pos >= 0; --pos) { DocumentChange& change(*sortedChanges[pos]); QString encountered; if(changeIsValid(change, textLines) && //We demand this, although it should be fixed ((encountered = textLines[change.m_range.start.line].mid(change.m_range.start.column, change.m_range.end.column-change.m_range.start.column)) == change.m_oldText || change.m_ignoreOldText)) { ///Problem: This does not work if the other changes significantly alter the context @todo Use the changed context QString leftContext = QStringList(textLines.mid(0, change.m_range.start.line+1)).join("\n"); leftContext.chop(textLines[change.m_range.start.line].length() - change.m_range.start.column); QString rightContext = QStringList(textLines.mid(change.m_range.end.line)).join("\n").mid(change.m_range.end.column); if(formatter && formatPolicy == DocumentChangeSet::AutoFormatChanges) change.m_newText = formatter->formatSource(change.m_newText, mime, leftContext, rightContext); textLines[change.m_range.start.line].replace(change.m_range.start.column, change.m_range.end.column-change.m_range.start.column, change.m_newText); }else{ QString warningString = QString("Inconsistent change in %1 at %2:%3 -> %4:%5 = \"%6\"(encountered \"%7\") -> \"%8\"") .arg(file.str()).arg(change.m_range.start.line).arg(change.m_range.start.column) .arg(change.m_range.end.line).arg(change.m_range.end.column).arg(change.m_oldText) .arg(encountered).arg(change.m_newText); if(replacePolicy == DocumentChangeSet::IgnoreFailedChange) { //Just don't do the replacement }else if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) kWarning() << warningString; else return DocumentChangeSet::ChangeResult(warningString, sortedChanges[pos]); } } output = textLines.join("\n"); return true; } //Removes all duplicate changes for a single file, and then returns (via filteredChanges) the filtered duplicates DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::removeDuplicates(const IndexedString & file, QList & filteredChanges) { QMultiMap sortedChanges; foreach(const DocumentChangePointer &change, changes[file]) sortedChanges.insert(change->m_range.end, change); //Remove duplicates QMultiMap::iterator previous = sortedChanges.begin(); for(QMultiMap::iterator it = ++sortedChanges.begin(); it != sortedChanges.end(); ) { if(( *previous ) && ( *previous )->m_range.end > (*it)->m_range.start) { //intersection if(duplicateChanges(( *previous ), *it)) { //duplicate, remove one it = sortedChanges.erase(it); continue; } //When two changes contain each other, and the container change is set to ignore old text, then it should be safe to //just ignore the contained change, and apply the bigger change else if((*it)->m_range.contains(( *previous )->m_range) && (*it)->m_ignoreOldText ) { kDebug() << "Removing change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText << ", because it is contained by change: " << (*it)->m_oldText << "->" << (*it)->m_newText; sortedChanges.erase(previous); } //This case is for when both have the same end, either of them could be the containing range else if((*previous)->m_range.contains((*it)->m_range) && (*previous)->m_ignoreOldText ) { kDebug() << "Removing change: " << (*it)->m_oldText << "->" << (*it)->m_newText << ", because it is contained by change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText; it = sortedChanges.erase(it); continue; } else return DocumentChangeSet::ChangeResult( QString("Inconsistent change-request at %1; intersecting changes: \"%2\"->\"%3\"@%4:%5->%6:%7 & \"%8\"->\"%9\"@%10:%11->%12:%13 ") .arg(file.str(), ( *previous )->m_oldText, ( *previous )->m_newText).arg(( *previous )->m_range.start.line).arg(( *previous )->m_range.start.column) .arg(( *previous )->m_range.end.line).arg(( *previous )->m_range.end.column).arg((*it)->m_oldText, (*it)->m_newText).arg((*it)->m_range.start.line) .arg((*it)->m_range.start.column).arg((*it)->m_range.end.line).arg((*it)->m_range.end.column)); } previous = it; ++it; } filteredChanges = sortedChanges.values(); return true; } void DocumentChangeSetPrivate::updateFiles() { + ModificationRevisionSet::clearCache(); + foreach(const IndexedString& file, changes.keys()) + ModificationRevision::clearModificationCache(file); + if(updatePolicy != DocumentChangeSet::NoUpdate && ICore::self()) + { + // The active document should be updated first, so that the user sees the results instantly + if(ICore::self()->documentController()->activeDocument()) + ICore::self()->languageController()->backgroundParser()->addDocument(ICore::self()->documentController()->activeDocument()->url()); + + // If there are currently open documents that now need an update, update them too + foreach(const IndexedString& doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) { + DUChainReadLocker lock(DUChain::lock()); + TopDUContext* top = DUChainUtils::standardContextForUrl(doc.toUrl(), true); + if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) { + lock.unlock(); + ICore::self()->languageController()->backgroundParser()->addDocument(doc.toUrl()); + } + } + + // Eventually update _all_ affected files foreach(const IndexedString &file, changes.keys()) { if(!file.toUrl().isValid()) { kWarning() << "Trying to apply changes to an invalid document"; continue; } ICore::self()->languageController()->backgroundParser()->addDocument(file.toUrl()); - foreach(const IndexedString& doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) { - DUChainReadLocker lock(DUChain::lock()); - TopDUContext* top = DUChainUtils::standardContextForUrl(doc.toUrl()); - if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) { - lock.unlock(); - ICore::self()->languageController()->backgroundParser()->addDocument(doc.toUrl()); - } - } } + } } } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index 66fd72c0ac..aea9c121ff 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1752 +1,1752 @@ /* This is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include "duchainlock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "repositories/itemrepository.h" #include "waitforupdate.h" #include "referencecounting.h" #include "importers.h" Q_DECLARE_METATYPE(KDevelop::IndexedString) Q_DECLARE_METATYPE(KDevelop::IndexedTopDUContext) Q_DECLARE_METATYPE(KDevelop::ReferencedTopDUContext) namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times SpinLockData DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup EnvironmentInformationRequest(uint topContextIndex) : m_file(0), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainItemSystem::self().copy(*m_file->d_func(), *(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem)), true); Q_ASSERT((*(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))).m_range == m_file->d_func()->m_range); Q_ASSERT((*(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))).classId == m_file->d_func()->classId); Q_ASSERT((*(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))).m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(0) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; static DUChainPrivate* duChainPrivateSelf = 0; class DUChainPrivate { class CleanupThread : public QThread { public: CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { m_waitMutex.lock(); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit m_waitMutex.unlock(); wait(); } private: void run() { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; m_waitMutex.lock(); m_wait.wait(&m_waitMutex, 1000); m_waitMutex.unlock(); } if(m_stopRunning) break; //Just to make sure the cache is cleared periodically ModificationRevisionSet::clearCache(); m_data->doMoreCleanup(SOFT_CLEANUP_STEPS); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: DUChainPrivate() : m_chainsMutex(QMutex::Recursive), instance(0), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo("Environment Lists"), m_environmentInfo("Environment Information") { #if defined(TEST_NO_CLEANUP) m_cleanupDisabled = true; #endif duChainPrivateSelf = this; qRegisterMetaType("KDevelop::DUChainBasePointer"); qRegisterMetaType("KDevelop::DUContextPointer"); qRegisterMetaType("KDevelop::TopDUContextPointer"); qRegisterMetaType("KDevelop::DeclarationPointer"); qRegisterMetaType("KDevelop::FunctionDeclarationPointer"); qRegisterMetaType("KDevelop::IndexedString"); qRegisterMetaType("KDevelop::IndexedTopDUContext"); qRegisterMetaType("KDevelop::ReferencedTopDUContext"); notifier = new DUChainObserver(); instance = new DUChain(); m_cleanup = new CleanupThread(this); m_cleanup->start(); DUChain::m_deleted = false; ///Loading of some static data: { ///@todo Solve this more duchain-like QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::ReadOnly); ParsingEnvironmentFile::m_staticData = (StaticParsingEnvironmentData*) new char[sizeof(StaticParsingEnvironmentData)]; if(opened) { kDebug() << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ kDebug() << "creating new parsing-environment static data"; //Initialize new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData(); } } ///Read in the list of available top-context indices { QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::ReadOnly); if(opened) { Q_ASSERT( (f.size() % sizeof(uint)) == 0); m_availableTopContextIndices.resize(f.size()/sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { kDebug() << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); QMutexLocker l(&m_chainsMutex); DUChainWriteLocker writeLock(DUChain::lock()); foreach(TopDUContext* top, m_chainsByUrl.values()) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded kDebug() << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // kDebug(9505) << "duchain: removing document" << context->url().str(); Q_ASSERT(hasChainForIndex(index)); Q_ASSERT(m_chainsByUrl.contains(context->url(), context)); m_chainsByUrl.remove(context->url(), context); if(!context->isOnDisk()) instance->removeFromEnvironmentManager(context); l.unlock(); //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it context->deleteSelf(); l.relock(); Q_ASSERT(hasChainForIndex(index)); SpinLock<> lock(DUChain::chainsByIndexLock); DUChain::chainsByIndex[index] = 0; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; DUChainObserver* notifier; Definitions m_definitions; Uses m_uses; QSet m_loading; bool m_cleanupDisabled; //List of available top-context indices, protected by m_chainsMutex QVector m_availableTopContextIndices; ///Used to keep alive the top-context that belong to documents loaded in the editor QSet m_openDocumentContexts; bool m_destroyed; ///The item must not be stored yet ///m_chainsMutex should not be locked, since this can trigger I/O void addEnvironmentInformation(ParsingEnvironmentFilePointer info) { Q_ASSERT(!findInformation(info->indexedTopContext().index())); Q_ASSERT(m_environmentInfo.findIndex(info->indexedTopContext().index()) == 0); QMutexLocker lock(&m_chainsMutex); m_fileEnvironmentInformations.insert(info->url(), info); m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info); Q_ASSERT(info->d_func()->classId); } ///The item must be managed currently ///m_chainsMutex does not need to be locked void removeEnvironmentInformation(ParsingEnvironmentFilePointer info) { info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor m_chainsMutex.lock(); bool removed = m_fileEnvironmentInformations.remove(info->url(), info); bool removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); m_chainsMutex.unlock(); { //Remove it from the environment information lists if it was there QMutexLocker lock(m_environmentListInfo.mutex()); uint index = m_environmentListInfo.findIndex(info->url()); if(index) { EnvironmentInformationListItem item(*m_environmentListInfo.itemFromIndex(index)); if(item.itemsList().removeOne(info->indexedTopContext().index())) { m_environmentListInfo.deleteItem(index); if(!item.itemsList().isEmpty()) m_environmentListInfo.index(EnvironmentInformationListRequest(info->url(), item)); } } } QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(info->indexedTopContext().index()); if(index) { m_environmentInfo.deleteItem(index); } Q_ASSERT(index || (removed && removed2)); Q_ASSERT(!findInformation(info->indexedTopContext().index())); } ///m_chainsMutex should _not_ be locked, because this may trigger I/O QList getEnvironmentInformation(IndexedString url) { QList ret; uint listIndex = m_environmentListInfo.findIndex(url); if(listIndex) { KDevVarLengthArray topContextIndices; { //First store all the possible intices into the KDevVarLengthArray, so we can unlock the mutex before processing them. QMutexLocker lock(m_environmentListInfo.mutex()); //Lock the mutex to make sure the item isn't changed while it's being iterated const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(listIndex); FOREACH_FUNCTION(uint topContextIndex, item->items) topContextIndices << topContextIndex; } //Process the indices in a separate step after copying them from the array, so we don't need m_environmentListInfo.mutex locked, //and can call loadInformation(..) safely, which else might lead to a deadlock. FOREACH_ARRAY(uint topContextIndex, topContextIndices) { KSharedPtr< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ kDebug() << "Failed to load enviromment-information for" << TopDUContextDynamicData::loadUrl(topContextIndex).str(); } } } QMutexLocker l(&m_chainsMutex); //Add those information that have not been added to the stored lists yet foreach(ParsingEnvironmentFilePointer file, m_fileEnvironmentInformations.values(url)) if(!ret.contains(file)) ret << file; return ret; } ///Must be called _without_ the chainsByIndex spin-lock locked static inline bool hasChainForIndex(uint index) { SpinLock<> lock(DUChain::chainsByIndexLock); return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index]; } ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded. static inline TopDUContext* readChainForIndex(uint index) { SpinLock<> lock(DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else return 0; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); kDebug() << "waiting for another thread to load index" << index; usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); kDebug() << "loading top-context" << index; TopDUContext* chain = TopDUContextDynamicData::load(index); if(chain) { chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex())); if(!chain->usingImportsCache()) { //Eventually also load all the imported chains, so the import-structure is built foreach(const DUContext::Import &import, chain->DUContext::importedParentContexts()) { if(!loaded.contains(import.topContextIndex())) { loadChain(import.topContextIndex(), loaded); } } } chain->rebuildDynamicImportStructure(); chain->setInDuChain(true); instance->addDocumentChain(chain); } l.relock(); m_loading.remove(index); } } ///Stores all environment-information ///Also makes sure that all information that stays is referenced, so it stays alive. ///@param atomic If this is false, the write-lock will be released time by time void storeAllInformation(bool atomic, DUChainWriteLocker& locker) { uint cnt = 0; QList urls; { QMutexLocker lock(&m_chainsMutex); urls += m_fileEnvironmentInformations.keys(); } foreach(const IndexedString &url, urls) { QList check; { QMutexLocker lock(&m_chainsMutex); check = m_fileEnvironmentInformations.values(url); } foreach(ParsingEnvironmentFilePointer file, check) { EnvironmentInformationRequest req(file.data()); QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(req); if(file->d_func()->isDynamic()) { //This item has been changed, or isn't in the repository yet //Eventually remove an old entry if(index) m_environmentInfo.deleteItem(index); //Add the new entry to the item repository index = m_environmentInfo.index(req); Q_ASSERT(index); EnvironmentInformationItem* item = const_cast(m_environmentInfo.itemFromIndex(index)); DUChainBaseData* theData = (DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem)); static DUChainBaseData* dataCopy; dataCopy = theData; Q_ASSERT(theData->m_range == file->d_func()->m_range); Q_ASSERT(theData->m_dynamic == false); Q_ASSERT(theData->classId == file->d_func()->classId); file->setData( theData ); ++cnt; }else{ m_environmentInfo.itemFromIndex(index); //Prevent unloading of the data, by accessing the item } } ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an ///information, and will get crashes. if(!atomic && (cnt % 100 == 0)) { //Release the lock on a regular basis locker.unlock(); locker.lock(); } storeInformationList(url); //Access the data in the repository, so the bucket isn't unloaded uint index = m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)); if(index) { m_environmentListInfo.itemFromIndex(index); }else{ QMutexLocker lock(&m_chainsMutex); kDebug(9505) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { static QMutex mutex(QMutex::Recursive); return mutex; } ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, bool needLockRepository = true) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); PersistentSymbolTable::self().clearCache(); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList lockedParseMutexes; QList locked; if(needLockRepository) { if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) lockedParseMutexes = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(ILanguage* language, lockedParseMutexes) { language->parseLock()->lockForWrite(); locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); kDebug(9505) << "starting cleanup"; } QTime startTime = QTime::currentTime(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); foreach(TopDUContext* top, m_chainsByUrl.values()) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between usleep(500); writeLock.lock(); } } //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one QSet unloadedNames; bool unloadedOne = true; bool unloadAllUnreferenced = !retries; //Now unload contexts, but only ones that are not imported by any other currently loaded context //The complication: Since during the lock-break new references may be added, we must never keep //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any //currently loaded contexts. In case of loops, we have to unload everything at once. while(unloadedOne) { unloadedOne = false; int hadUnloadable = 0; unloadContexts: foreach(TopDUContext* unload, workOnContexts) { bool hasReference = false; { QMutexLocker l(&m_referenceCountsMutex); //Test if the context is imported by a referenced one foreach(TopDUContext* context, m_referenceCounts.keys()) { if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between usleep(500); writeLock.lock(); } } if(hadUnloadable && !unloadedOne) { Q_ASSERT(!unloadAllUnreferenced); //This can happen in case of loops. We have o unload everything at one time. kDebug(9505) << "found" << hadUnloadable << "unloadable contexts, but could not unload separately. Unloading atomically."; unloadAllUnreferenced = true; hadUnloadable = 0; //Reset to 0, so we cannot loop forever goto unloadContexts; } } if(retries == 0) { QMutexLocker lock(&m_chainsMutex); //Do this atomically, since we must be sure that _everything_ is already saved for(QMultiMap::iterator it = m_fileEnvironmentInformations.begin(); it != m_fileEnvironmentInformations.end(); ) { ParsingEnvironmentFile* f = (*it).data(); Q_ASSERT(f->d_func()->classId); if(f->ref == 1) { Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any //loaded top-context, so just remove it to save some memory and processing time. ///@todo use some kind of timeout before removing it = m_fileEnvironmentInformations.erase(it); }else{ ++it; } } } if(retries) writeLock.unlock(); //This must be the last step, due to the on-disk reference counting globalItemRepositoryRegistry().store(); //Stores all repositories { //Store the static parsing-environment file data ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data Q_ASSERT(ParsingEnvironmentFile::m_staticData); QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); f.write((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); } ///Write out the list of available top-context indices { QMutexLocker lock(&m_chainsMutex); QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); f.write((char*)m_availableTopContextIndices.data(), m_availableTopContextIndices.size() * sizeof(uint)); } if(retries) { doMoreCleanup(retries-1, false); writeLock.lock(); } if(needLockRepository) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); kDebug(9505) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); kDebug(9505) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); } ///Checks whether the information is already loaded. ParsingEnvironmentFile* findInformation(uint topContextIndex) { QMutexLocker lock(&m_chainsMutex); QHash::iterator it = m_indexEnvironmentInformations.find(topContextIndex); if(it != m_indexEnvironmentInformations.end()) return (*it).data(); return 0; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context return 0; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( (DUChainBaseData*)(((char*)&item) + sizeof(EnvironmentInformationItem)) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); kDebug() << "cleaning top-contexts"; CleanupListVisitor visitor; uint startPos = 0; m_environmentInfo.visitAllItems(visitor); int checkContextsCount = maxFinalCleanupCheckContexts; int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage; if(checkContextsCount < percentageOfContexts) checkContextsCount = percentageOfContexts; if(visitor.checkContexts.size() > (int)checkContextsCount) startPos = qrand() % (visitor.checkContexts.size() - checkContextsCount); int endPos = startPos + maxFinalCleanupCheckContexts; if(endPos > visitor.checkContexts.size()) endPos = visitor.checkContexts.size(); QSet< uint > check; for(int a = startPos; a < endPos && check.size() < checkContextsCount; ++a) if(check.size() < checkContextsCount) addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a])); foreach(uint topIndex, check) { IndexedTopDUContext top(topIndex); if(top.data()) { kDebug() << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } kDebug() << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; KSharedPtr info( instance->environmentFileForDocument(top) ); ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...) if(info && info->needsUpdate()) { //This context will be removed }else{ return; } topContexts.insert(top.index()); if(info) { //Check whether importers need to be removed as well QList< KSharedPtr > importers = info->importers(); QSet< KSharedPtr > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< KSharedPtr >::iterator it = importers.begin(); it != importers.end(); ++it) { IndexedTopDUContext c = (*it)->indexedTopContext(); if(!topContexts.contains(c.index())) { topContexts.insert(c.index()); //Prevent useless recursion checkNext.insert(*it); } } for(QSet< KSharedPtr >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } template bool listContains(const Entry entry, const Entry* list, uint listSize) { for(uint a = 0; a < listSize; ++a) if(list[a] == entry) return true; return false; } ///Stores the environment-information for the given url void storeInformationList(IndexedString url) { QMutexLocker lock(m_environmentListInfo.mutex()); EnvironmentInformationListItem newItem; newItem.m_file = url; QSet newItems; { QMutexLocker lock(&m_chainsMutex); QMultiMap::iterator start = m_fileEnvironmentInformations.lowerBound(url); QMultiMap::iterator end = m_fileEnvironmentInformations.upperBound(url); for(QMultiMap::iterator it = start; it != end; ++it) { uint topContextIndex = (*it)->indexedTopContext().index(); newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } uint index = m_environmentListInfo.findIndex(url); if(index) { //We only handle adding items here, since we can never be sure whether everything is loaded //Removal is handled directly in removeEnvironmentInformation const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(index); QSet oldItems; FOREACH_FUNCTION(uint topContextIndex, item->items) { oldItems.insert(topContextIndex); if(!newItems.contains(topContextIndex)) { newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } if(oldItems == newItems) return; ///Update/insert a new list m_environmentListInfo.deleteItem(index); //Remove the previous item } Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)) == 0); //Insert the new item m_environmentListInfo.index(EnvironmentInformationListRequest(url, newItem)); Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url))); } //Loaded environment-informations. Protected by m_chainsMutex QMultiMap m_fileEnvironmentInformations; QHash m_indexEnvironmentInformations; ///The following repositories are thread-safe, and m_chainsMutex should not be locked when using them, because ///they may trigger I/O. Still it may be required to lock their local mutexes. ///Maps filenames to a list of top-contexts/environment-information. ItemRepository m_environmentListInfo; ///Maps top-context-indices to environment-information item. ItemRepository m_environmentInfo; }; K_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); if(ICore::self()) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), SIGNAL(documentLoadedPrepare(KDevelop::IDocument*)), this, SLOT(documentLoadedPrepare(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(documentRenamed(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(documentActivated(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*))); } } DUChain::~DUChain() { DUChain::m_deleted = true; } DUChain* DUChain::self() { return sdDUChainPrivate->instance; } extern void initModificationRevisionSetRepository(); extern void initDeclarationRepositories(); extern void initIdentifierRepository(); extern void initTypeRepository(); extern void initInstantiationInformationRepository(); extern void initReferenceCounting(); void DUChain::initialize() { // Initialize the global item repository as first thing after loading the session globalItemRepositoryRegistry(); initReferenceCounting(); // This needs to be initialized here too as the function is not threadsafe, but can // sometimes be called from different threads. This results in the underlying QFile // being 0 and hence crashes at some point later when accessing the contents via // read. See https://bugs.kde.org/show_bug.cgi?id=250779 RecursiveImportRepository::repository(); RecursiveImportCacheRepository::repository(); // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323 initDeclarationRepositories(); initModificationRevisionSetRepository(); initIdentifierRepository(); initTypeRepository(); initInstantiationInformationRepository(); Importers::self(); globalImportIdentifier(); globalAliasIdentifier(); } DUChainLock* DUChain::lock() { return &sdDUChainPrivate->lock; } QList DUChain::allChains() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); return sdDUChainPrivate->m_chainsByUrl.values(); } void DUChain::updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); removeFromEnvironmentManager( context ); context->setParsingEnvironmentFile( file ); addToEnvironmentManager( context ); branchModified(context); } void DUChain::removeDocumentChain( TopDUContext* context ) { ENSURE_CHAIN_WRITE_LOCKED; IndexedTopDUContext indexed(context->indexed()); Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain branchRemoved(context); context->m_dynamicData->deleteOnDisk(); Q_ASSERT(indexed.data() == context); sdDUChainPrivate->removeDocumentChainFromMemory(context); Q_ASSERT(!indexed.data()); Q_ASSERT(!environmentFileForDocument(indexed)); QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index()); } void DUChain::addDocumentChain( TopDUContext * chain ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // kDebug(9505) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { SpinLock<> lock(DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) DUChain::chainsByIndex.resize(chain->ownIndex() + 100, 0); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); //contextChanged(0L, DUChainObserver::Addition, DUChainObserver::ChildContexts, chain); if(ICore::self() && ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) { //Make sure the context stays alive at least as long as the context is open ReferencedTopDUContext ctx(chain); sdDUChainPrivate->m_openDocumentContexts.insert(ctx); } branchAdded(chain); } void DUChain::addToEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex()); if(ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) { ///If this triggers, there has already been another environment-information registered for this top-context. ///removeFromEnvironmentManager should have been called before to remove the old environment-information. Q_ASSERT(alreadyHave == file.data()); return; } sdDUChainPrivate->addEnvironmentInformation(file); } void DUChain::removeFromEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage sdDUChainPrivate->removeEnvironmentInformation(file); } TopDUContext* DUChain::chainForDocument(const KUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document.pathOrUrl()), proxyContext); } bool DUChain::isInMemory(uint topContextIndex) const { return DUChainPrivate::hasChainForIndex(topContextIndex); } IndexedString DUChain::urlForIndex(uint index) const { { TopDUContext* chain = DUChainPrivate::readChainForIndex(index); if(chain) return chain->url(); } return TopDUContextDynamicData::loadUrl(index); } TopDUContext* DUChain::loadChain(uint index) { QSet loaded; sdDUChainPrivate->loadChain(index, loaded); { SpinLock<> lock(chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } return 0; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return 0; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); /* { int count = 0; QMap::Iterator it = sdDUChainPrivate->m_chains.lowerBound(document); for( ; it != sdDUChainPrivate->m_chains.end() && it.key().url() == document.url(); ++it ) ++count; if( count > 1 ) kDebug(9505) << "found " << count << " chains for " << document.url().str(); }*/ //Eventually load an existing chain from disk l.unlock(); QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if((proxyContext == file->isProxyContext())) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } return 0; } QList DUChain::chainsForDocument(const KUrl& document) const { return chainsForDocument(IndexedString(document)); } QList DUChain::chainsForDocument(const IndexedString& document) const { QList chains; if(sdDUChainPrivate->m_destroyed) return chains; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // Match all parsed versions of this document for (QMultiMap::Iterator it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end(); ++it) { if (it.key() == document) chains << it.value(); else break; } return chains; } TopDUContext* DUChain::chainForDocument( const KUrl& document, const KDevelop::ParsingEnvironment* environment, bool proxyContext ) const { return chainForDocument( IndexedString(document), environment, proxyContext ); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return ParsingEnvironmentFilePointer(); QList< ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document); // kDebug() << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); QList< ParsingEnvironmentFilePointer>::const_iterator it = list.constBegin(); while(it != list.constEnd()) { if(*it && ((*it)->isProxyContext() == proxyContext) && (*it)->matchEnvironment(environment)) { return *it; } ++it; } return ParsingEnvironmentFilePointer(); } QList DUChain::allEnvironmentFiles(const IndexedString& document) { return sdDUChainPrivate->getEnvironmentInformation(document); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const { if(topContext.index() == 0) return ParsingEnvironmentFilePointer(); return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index())); } TopDUContext* DUChain::chainForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { if(sdDUChainPrivate->m_destroyed) return 0; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return 0; } } DUChainObserver* DUChain::notifier() { return sdDUChainPrivate->notifier; } void DUChain::branchAdded(DUContext* context) { emit sdDUChainPrivate->notifier->branchAdded(DUContextPointer(context)); } void DUChain::branchModified(DUContext* context) { emit sdDUChainPrivate->notifier->branchModified(DUContextPointer(context)); } void DUChain::branchRemoved(DUContext* context) { emit sdDUChainPrivate->notifier->branchRemoved(DUContextPointer(context)); } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url().toUrl(); } return ret; } /*Q_SCRIPTABLE bool DUChain::updateContext(TopDUContext* topContext, TopDUContext::Features minFeatures, QObject* notifyReady) const { if( (topContext->features() & minFeatures) != minFeatures || (topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->needsUpdate()) ) { ICore::self()->languageController()->backgroundParser()->addUpdateJob(topContext, minFeatures, notifyReady); return true; }else{ //No update needed, or we don't know since there's no ParsingEnvironmentFile attached return false; } }*/ void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); - TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url()); + TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); if(ctx && ctx->parsingEnvironmentFile()) if(ctx->parsingEnvironmentFile()->needsUpdate()) ICore::self()->languageController()->backgroundParser()->addDocument(doc->url()); } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(doc->url()); QList languages = ICore::self()->languageController()->languagesForUrl(doc->url()); if(standardContext) { Q_ASSERT(chains.contains(standardContext)); //We have just loaded it Q_ASSERT((standardContext->url().toUrl() == doc->url())); sdDUChainPrivate->m_openDocumentContexts.insert(standardContext); bool needsUpdate = standardContext->parsingEnvironmentFile() && standardContext->parsingEnvironmentFile()->needsUpdate(); if(!needsUpdate) { //Only apply the highlighting if we don't need to update, else we might highlight total crap //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long //Else the highlighting will be done in the background-thread //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses //but it approximates the correct behavior. bool allImportsLoaded = true; foreach(const DUContext::Import& import, standardContext->importedParentContexts()) if(!import.indexedContext().indexedTopContext().isLoaded()) allImportsLoaded = false; if(allImportsLoaded) { foreach( KDevelop::ILanguage* language, languages) if(language->languageSupport() && language->languageSupport()->codeHighlighting()) language->languageSupport()->codeHighlighting()->highlightDUChain(standardContext); kDebug() << "highlighted" << doc->url() << "in foreground"; return; } }else{ kDebug() << "not highlighting the duchain because the documents needs an update"; } if(needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ICore::self()->languageController()->backgroundParser()->addDocument(doc->url(), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); return; } } //Add for highlighting etc. ICore::self()->languageController()->backgroundParser()->addDocument(doc->url(), TopDUContext::AllDeclarationsContextsAndUses); } void DUChain::documentRenamed(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; if(!doc->url().isValid()) { ///Maybe this happens when a file was deleted? kWarning() << "Strange, url of renamed document is invalid!"; }else{ ICore::self()->languageController()->backgroundParser()->addDocument(doc->url(), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); } } Uses* DUChain::uses() { return &sdDUChainPrivate->m_uses; } Definitions* DUChain::definitions() { return &sdDUChainPrivate->m_definitions; } void DUChain::aboutToQuit() { QMutexLocker lock(&sdDUChainPrivate->cleanupMutex()); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); sdDUChainPrivate->cleanupTopContexts(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); finalCleanup(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); globalItemRepositoryRegistry().shutdown(); } uint DUChain::newTopContextIndex() { { QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); if(!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) { uint ret = sdDUChainPrivate->m_availableTopContextIndices.back(); sdDUChainPrivate->m_availableTopContextIndices.pop_back(); if(TopDUContextDynamicData::fileExists(ret)) { kWarning() << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter("Top-Context Counter", 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) sdDUChainPrivate->m_referenceCounts.insert(top, 1); else ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) { //kWarning() << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } --sdDUChainPrivate->m_referenceCounts[top]; if(!sdDUChainPrivate->m_referenceCounts[top]) sdDUChainPrivate->m_referenceCounts.remove(top); } void DUChain::emitDeclarationSelected(DeclarationPointer decl) { emit declarationSelected(decl); } TopDUContext* contentContextFromProxyContext(TopDUContext* top) { if(!top) return 0; if(top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->isProxyContext()) { if(!top->importedParentContexts().isEmpty()) { TopDUContext* ret = top->importedParentContexts()[0].context(0)->topContext(); if(ret->url() != top->url()) kDebug() << "url-mismatch between content and proxy:" << top->url().toUrl() << ret->url().toUrl(); if(ret->url() == top->url() && !ret->parsingEnvironmentFile()->isProxyContext()) return ret; } else { kDebug() << "Proxy-context imports no content-context"; } } else return top; return 0; } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; waiter.m_dataMutex.lock(); { DUChainReadLocker readLock(DUChain::lock()); updateContextForUrl(document, minFeatures, &waiter); } // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { TemporarilyReleaseForegroundLock release; ///@todo When we don't do this, the backgroundparser doesn't process anything. /// The background-parser should be moved into an own thread, so we wouldn't need to do this. QApplication::processEvents(); usleep(10000); // waiter.m_wait.wait(&waiter.m_waitMutex, 10); } if(!proxyContext) { DUChainReadLocker readLock(DUChain::lock()); return contentContextFromProxyContext(waiter.m_topContext); } return waiter.m_topContext; } void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady, int priority) const { DUChainReadLocker lock( DUChain::lock() ); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl()); if(standardContext && standardContext->parsingEnvironmentFile() && !standardContext->parsingEnvironmentFile()->needsUpdate() && standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) { lock.unlock(); if(notifyReady) QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection, Q_ARG(KDevelop::IndexedString, document), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext))); }else{ ///Start a parse-job for the given document ICore::self()->languageController()->backgroundParser()->addDocument(document.toUrl(), minFeatures, priority, notifyReady); } } void DUChain::disablePersistentStorage() { sdDUChainPrivate->m_cleanupDisabled = true; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } void DUChain::finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); kDebug() << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { kDebug() << "cleaned" << cleaned << "B"; if(cleaned < 1000) { kDebug() << "cleaned enough"; break; } } kDebug() << "final cleanup ready"; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } #include "duchain.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/duchainutils.cpp b/language/duchain/duchainutils.cpp index b75ddafcf3..c75cce3630 100644 --- a/language/duchain/duchainutils.cpp +++ b/language/duchain/duchainutils.cpp @@ -1,561 +1,564 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainutils.h" #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "declaration.h" #include "classfunctiondeclaration.h" #include "ducontext.h" #include "duchain.h" #include "use.h" #include "duchainlock.h" #include "classmemberdeclaration.h" #include "functiondefinition.h" #include "specializationstore.h" #include "persistentsymboltable.h" #include "classdeclaration.h" using namespace KDevelop; using namespace KTextEditor; CodeCompletionModel::CompletionProperties DUChainUtils::completionProperties(const Declaration* dec) { CodeCompletionModel::CompletionProperties p; if(dec->context()->type() == DUContext::Class) { if (const ClassMemberDeclaration* member = dynamic_cast(dec)) { switch (member->accessPolicy()) { case Declaration::Public: p |= CodeCompletionModel::Public; break; case Declaration::Protected: p |= CodeCompletionModel::Protected; break; case Declaration::Private: p |= CodeCompletionModel::Private; break; default: break; } if (member->isStatic()) p |= CodeCompletionModel::Static; if (member->isAuto()) {}//TODO if (member->isFriend()) p |= CodeCompletionModel::Friend; if (member->isRegister()) {}//TODO if (member->isExtern()) {}//TODO if (member->isMutable()) {}//TODO } } if (const AbstractFunctionDeclaration* function = dynamic_cast(dec)) { p |= CodeCompletionModel::Function; if (function->isVirtual()) p |= CodeCompletionModel::Virtual; if (function->isInline()) p |= CodeCompletionModel::Inline; if (function->isExplicit()) {}//TODO } if( dec->isTypeAlias() ) p |= CodeCompletionModel::TypeAlias; if (dec->abstractType()) { switch (dec->abstractType()->whichType()) { case AbstractType::TypeIntegral: p |= CodeCompletionModel::Variable; break; case AbstractType::TypePointer: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeReference: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeFunction: p |= CodeCompletionModel::Function; break; case AbstractType::TypeStructure: p |= CodeCompletionModel::Class; break; case AbstractType::TypeArray: p |= CodeCompletionModel::Variable; break; case AbstractType::TypeEnumeration: case AbstractType::TypeEnumerator: p |= CodeCompletionModel::Enum; break; case AbstractType::TypeAbstract: case AbstractType::TypeDelayed: case AbstractType::TypeUnsure: case AbstractType::TypeAlias: // TODO break; } if( dec->abstractType()->modifiers() & AbstractType::ConstModifier ) p |= CodeCompletionModel::Const; if( dec->kind() == Declaration::Instance && !dec->isFunctionDeclaration() ) p |= CodeCompletionModel::Variable; } if (dec->context()) { if( dec->context()->type() == DUContext::Global ) p |= CodeCompletionModel::GlobalScope; else if( dec->context()->type() == DUContext::Namespace ) p |= CodeCompletionModel::NamespaceScope; else if( dec->context()->type() != DUContext::Class && dec->context()->type() != DUContext::Enum ) p |= CodeCompletionModel::LocalScope; } return p; } /**We have to construct the item from the pixmap, else the icon will be marked as "load on demand", * and for some reason will be loaded every time it's used(this function returns a QIcon marked "load on demand" * each time this is called). And the loading is very slow. Seems like a bug somewhere, it cannot be ment to be that slow. */ #define RETURN_CACHED_ICON(name) {static QIcon icon(KIcon(name).pixmap(QSize(16, 16))); return icon;} QIcon DUChainUtils::iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p) { if( (p & CodeCompletionModel::Variable) ) if( (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("CVprotected_var") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_var") else RETURN_CACHED_ICON("CVpublic_var") else if( (p & CodeCompletionModel::Union) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_union") else if( p & CodeCompletionModel::Enum ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_enum") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_enum") else RETURN_CACHED_ICON("enum") else if( p & CodeCompletionModel::Struct ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_struct") else RETURN_CACHED_ICON("struct") else if( p & CodeCompletionModel::Slot ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_slot") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("CVprivate_slot") else if(p & CodeCompletionModel::Public ) RETURN_CACHED_ICON("CVpublic_slot") else RETURN_CACHED_ICON("slot") else if( p & CodeCompletionModel::Signal ) if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("CVprotected_signal") else RETURN_CACHED_ICON("signal") else if( p & CodeCompletionModel::Class ) if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Protected) ) RETURN_CACHED_ICON("protected_class") else if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Private) ) RETURN_CACHED_ICON("private_class") else RETURN_CACHED_ICON("code-class") else if( p & CodeCompletionModel::Union ) if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_union") else RETURN_CACHED_ICON("union") else if( p & CodeCompletionModel::TypeAlias ) if ((p & CodeCompletionModel::Const) /*|| (p & CodeCompletionModel::Volatile)*/) RETURN_CACHED_ICON("CVtypedef") else RETURN_CACHED_ICON("typedef") else if( p & CodeCompletionModel::Function ) { if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_function") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_function") else RETURN_CACHED_ICON("code-function") } if( p & CodeCompletionModel::Protected ) RETURN_CACHED_ICON("protected_field") else if( p & CodeCompletionModel::Private ) RETURN_CACHED_ICON("private_field") else RETURN_CACHED_ICON("field") return KIcon(); } QIcon DUChainUtils::iconForDeclaration(const Declaration* dec) { return iconForProperties(completionProperties(dec)); } -TopDUContext* DUChainUtils::standardContextForUrl(const KUrl& url) { +TopDUContext* DUChainUtils::standardContextForUrl(const KUrl& url, bool preferProxyContext) { KDevelop::TopDUContext* chosen = 0; QList languages = ICore::self()->languageController()->languagesForUrl(url); foreach( KDevelop::ILanguage* language, languages) { if(!chosen) { if (language->languageSupport()) - chosen = language->languageSupport()->standardContext(url); + chosen = language->languageSupport()->standardContext(url, preferProxyContext); } } if(!chosen) - return DUChain::self()->chainForDocument(IndexedString(url.pathOrUrl())); + chosen = DUChain::self()->chainForDocument(IndexedString(url.pathOrUrl()), preferProxyContext); + if(!chosen && preferProxyContext) + return standardContextForUrl(url, false); // Fall back to a normal context + return chosen; } Declaration* declarationUnderCursor(const CursorInRevision& c, DUContext* ctx) { foreach( Declaration* decl, ctx->localDeclarations() ) if( decl->range().contains(c) ) return decl; //Search all collapsed sub-contexts. In C++, those can contain declarations that have ranges out of the context foreach( DUContext* subCtx, ctx->childContexts() ) { //This is a little hacky, but we need it in case of foreach macros and similar stuff if(subCtx->range().isEmpty() || subCtx->range().start.line == c.line || subCtx->range().end.line == c.line) { Declaration* decl = declarationUnderCursor(c, subCtx); if(decl) return decl; } } return 0; } Declaration* DUChainUtils::itemUnderCursor(const KUrl& url, const KDevelop::SimpleCursor& _c) { KDevelop::TopDUContext* chosen = standardContextForUrl(url); if( chosen ) { CursorInRevision c = chosen->transformToLocalRevision(_c); DUContext* ctx = chosen->findContextAt(c); while( ctx ) { //Try finding a declaration under the cursor Declaration* decl = declarationUnderCursor(c, ctx); if(decl) return decl; //Try finding a use under the cursor for(int a = 0; a < ctx->usesCount(); ++a) if( ctx->uses()[a].m_range.contains(c) ) return ctx->topContext()->usedDeclarationForIndex(ctx->uses()[a].m_declarationIndex); ctx = ctx->parentContext(); //It may happen, for example in the case of function-declarations, that the use is one context higher. } } return 0; } Declaration* DUChainUtils::declarationForDefinition(Declaration* definition, TopDUContext* topContext) { if(!definition) return 0; if(!topContext) topContext = definition->topContext(); if(dynamic_cast(definition)) { Declaration* ret = static_cast(definition)->declaration(); if(ret) return ret; } return definition; } Declaration* DUChainUtils::declarationInLine(const KDevelop::SimpleCursor& _cursor, DUContext* ctx) { if(!ctx) return 0; CursorInRevision cursor = ctx->transformToLocalRevision(_cursor); foreach(Declaration* decl, ctx->localDeclarations()) { if(decl->range().start.line == cursor.line) return decl; DUContext* funCtx = getFunctionContext(decl); if(funCtx && funCtx->range().contains(cursor)) return decl; } foreach(DUContext* child, ctx->childContexts()){ Declaration* decl = declarationInLine(_cursor, child); if(decl) return decl; } return 0; } DUChainUtils::DUChainItemFilter::~DUChainItemFilter() { } void DUChainUtils::collectItems( DUContext* context, DUChainItemFilter& filter ) { QVector children = context->childContexts(); QVector localDeclarations = context->localDeclarations(); QVector::const_iterator childIt = children.constBegin(); QVector::const_iterator declIt = localDeclarations.constBegin(); while(childIt != children.constEnd() || declIt != localDeclarations.constEnd()) { DUContext* child = 0; if(childIt != children.constEnd()) child = *childIt; Declaration* decl = 0; if(declIt != localDeclarations.constEnd()) decl = *declIt; if(decl) { if(child && child->range().start.line >= decl->range().start.line) child = 0; } if(child) { if(decl && decl->range().start >= child->range().start) decl = 0; } if(decl) { if( filter.accept(decl) ) { //Action is done in the filter } ++declIt; continue; } if(child) { if( filter.accept(child) ) collectItems(child, filter); ++childIt; continue; } } } KDevelop::DUContext* DUChainUtils::getArgumentContext(KDevelop::Declaration* decl) { DUContext* internal = decl->internalContext(); if( !internal ) return 0; if( internal->type() == DUContext::Function ) return internal; foreach( const DUContext::Import &ctx, internal->importedParentContexts() ) { if( ctx.context(decl->topContext()) ) if( ctx.context(decl->topContext())->type() == DUContext::Function ) return ctx.context(decl->topContext()); } return 0; } QList DUChainUtils::collectAllVersions(Declaration* decl) { QList ret; ret << IndexedDeclaration(decl); if(decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) if(!(allDeclarations[a] == IndexedDeclaration(decl))) ret << allDeclarations[a]; } return ret; } ///For a class, returns all classes that inherit it QList DUChainUtils::getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions) { QList ret; if(!dynamic_cast(decl)) return ret; if(maxAllowedSteps == 0) return ret; if(decl->internalContext() && decl->internalContext()->type() == DUContext::Class) FOREACH_ARRAY(const IndexedDUContext& importer, decl->internalContext()->indexedImporters()) { DUContext* imp = importer.data(); if(!imp) continue; if(imp->type() == DUContext::Class && imp->owner()) ret << imp->owner(); --maxAllowedSteps; if(maxAllowedSteps == 0) return ret; } if(maxAllowedSteps == 0) return ret; if(collectVersions && decl->inSymbolTable()) { uint count; const IndexedDeclaration* allDeclarations; PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations); for(uint a = 0; a < count; ++a) { ++maxAllowedSteps; if(allDeclarations[a].data() && allDeclarations[a].data() != decl) { ret += getInheriters(allDeclarations[a].data(), maxAllowedSteps, false); } if(maxAllowedSteps == 0) return ret; } } return ret; } QList DUChainUtils::getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps) { QList ret; if(maxAllowedSteps == 0) return ret; if(currentClass != overriddenDeclaration->context()->owner() && currentClass->internalContext()) ret += currentClass->internalContext()->findLocalDeclarations(overriddenDeclaration->identifier(), CursorInRevision::invalid(), currentClass->topContext(), overriddenDeclaration->abstractType()); foreach(Declaration* inheriter, getInheriters(currentClass, maxAllowedSteps)) ret += getOverriders(inheriter, overriddenDeclaration, maxAllowedSteps); return ret; } static bool hasUse(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return false; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) return true; foreach(DUContext* child, context->childContexts()) if(hasUse(child, usedDeclarationIndex)) return true; return false; } bool DUChainUtils::contextHasUse(DUContext* context, Declaration* declaration) { return hasUse(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } static uint countUses(DUContext* context, int usedDeclarationIndex) { if(usedDeclarationIndex == std::numeric_limits::max()) return 0; uint ret = 0; for(int a = 0; a < context->usesCount(); ++a) if(context->uses()[a].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) ret += countUses(child, usedDeclarationIndex); return ret; } uint DUChainUtils::contextCountUses(DUContext* context, Declaration* declaration) { return countUses(context, context->topContext()->indexForUsedDeclaration(declaration, false)); } Declaration* DUChainUtils::getOverridden(const Declaration* decl) { const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl); if(!classFunDecl || !classFunDecl->isVirtual()) return 0; QList decls; foreach(const DUContext::Import &import, decl->context()->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx) decls += ctx->findDeclarations(QualifiedIdentifier(decl->identifier()), CursorInRevision::invalid(), decl->abstractType(), decl->topContext(), DUContext::DontSearchInParent); } foreach(Declaration* found, decls) { const ClassFunctionDeclaration* foundClassFunDecl = dynamic_cast(found); if(foundClassFunDecl && foundClassFunDecl->isVirtual()) return found; } return 0; } DUContext* DUChainUtils::getFunctionContext(Declaration* decl) { DUContext* functionContext = decl->internalContext(); if(functionContext && functionContext->type() != DUContext::Function) { foreach(const DUContext::Import& import, functionContext->importedParentContexts()) { DUContext* ctx = import.context(decl->topContext()); if(ctx && ctx->type() == DUContext::Function) functionContext = ctx; } } if(functionContext && functionContext->type() == DUContext::Function) return functionContext; return 0; } diff --git a/language/duchain/duchainutils.h b/language/duchain/duchainutils.h index 0ec0aa0d48..a5e6c82680 100644 --- a/language/duchain/duchainutils.h +++ b/language/duchain/duchainutils.h @@ -1,111 +1,112 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DUCHAINUTILS_H #define DUCHAINUTILS_H #include #include #include "../languageexport.h" class KUrl; namespace KDevelop { class Declaration; class DUChainBase; class DUContext; class SimpleCursor; class IndexedString; class TopDUContext; class IndexedDUContext; class IndexedDeclaration; /** * A namespace which contains convenience utilities for navigating definition-use chains. */ namespace DUChainUtils { KDEVPLATFORMLANGUAGE_EXPORT KTextEditor::CodeCompletionModel::CompletionProperties completionProperties(const Declaration* dec); KDEVPLATFORMLANGUAGE_EXPORT QIcon iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p); KDEVPLATFORMLANGUAGE_EXPORT QIcon iconForDeclaration(const Declaration* dec); /** Asks the language-plugins for standard-contexts for the given url, and returns one if available. * If there is no language-plugin registered for the given url, it will just try to get any top-context for the file from the du-chain. * NOTE: The DUChain needs to be read or write locked when you call this. + * @param proxyContext Whether the returned context should be a proxy context. When no proxy-context is found, a normal context is returned. */ - KDEVPLATFORMLANGUAGE_EXPORT KDevelop::TopDUContext* standardContextForUrl(const KUrl& url); + KDEVPLATFORMLANGUAGE_EXPORT KDevelop::TopDUContext* standardContextForUrl(const KUrl& url, bool preferProxyContext = false); /** Returns the Declaration/Definition under the cursor, or zero. DUChain does not need to be locked. * Must only be called from the foreground or with the foreground lock held. * If the item under the cursor is a use, the declaration is returned. */ KDEVPLATFORMLANGUAGE_EXPORT Declaration* itemUnderCursor(const KUrl& url, const SimpleCursor& cursor); /**If the given declaration is a definition, and has a real declaration *attached, returns that declarations. Else returns the given argument. */ KDEVPLATFORMLANGUAGE_EXPORT Declaration* declarationForDefinition(Declaration* definition, TopDUContext* topContext = 0); ///Returns the first declaration in the given line. Searches the given context and all sub-contexts. ///Must only be called from the foreground or with the foreground lock held. KDEVPLATFORMLANGUAGE_EXPORT Declaration* declarationInLine(const KDevelop::SimpleCursor& cursor, KDevelop::DUContext* ctx); class KDEVPLATFORMLANGUAGE_EXPORT DUChainItemFilter { public: virtual bool accept(Declaration* decl) = 0; //Should return whether processing should be deepened into the given context virtual bool accept(DUContext* ctx) = 0; virtual ~DUChainItemFilter(); }; ///walks a context, all its sub-contexts, and all its declarations in exactly the order they appear in in the file. ///Re-implement DUChainItemFilter to do something with the items. KDEVPLATFORMLANGUAGE_EXPORT void collectItems( DUContext* context, DUChainItemFilter& filter ); KDEVPLATFORMLANGUAGE_EXPORT DUContext* getArgumentContext(Declaration* decl); ///Uses the persistent symbol table to find all occurrences of this declaration, based on its identifier. ///The result should be filtered to make sure that the declaration is actually useful to you. KDEVPLATFORMLANGUAGE_EXPORT QList collectAllVersions(Declaration* decl); ///If the given declaration is a class, this gets all classes that inherit this one ///@param collectVersions If this is true, the persistent symbol table is used to first find all registered /// versions of this class, and then get the inheriters from them all together. This is needed for C++. ///@param maxAllowedSteps The maximum of steps allowed. If this is zero in the end, this means the search has been stopped with the max. reached /// If you really want _all_ inheriters, you should initialize it with a very large value. KDEVPLATFORMLANGUAGE_EXPORT QList getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions = true); ///Gets all functions that override the function @param overriddenDeclaration, starting the search at @param currentClass ///@param maxAllowedSteps The maximum of steps allowed. If this is zero in the end, this means the search has been stopped with the max. reached KDEVPLATFORMLANGUAGE_EXPORT QList getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps); ///Returns whether the given context or any of its child-contexts contain a use of the given declaration. This is relatively expensive. KDEVPLATFORMLANGUAGE_EXPORT bool contextHasUse(DUContext* context, Declaration* declaration); ///Returns the total count of uses of the given declaration under the given context KDEVPLATFORMLANGUAGE_EXPORT uint contextCountUses(DUContext* context, Declaration* declaration); ///Returns the declaration that is overridden by the given one, or zero. KDEVPLATFORMLANGUAGE_EXPORT Declaration* getOverridden(const Declaration* decl); ///If the given declaration is a function-declaration, this follows the context-structure up to the function-context that contains the arguments, ///and returns it. KDEVPLATFORMLANGUAGE_EXPORT DUContext* getFunctionContext(Declaration* decl); } } #endif // DUCHAINUTILS_H diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index ea75b23ab5..10355bb097 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,94 +1,94 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" #include #include #include #include #include #include #include #include -#include +#include using namespace KDevelop; ProblemNavigationContext::ProblemNavigationContext(ProblemPointer problem): m_problem(problem) { m_widget = 0; KSharedPtr< IAssistant > solution = problem->solutionAssistant(); if(solution) { m_widget = new QWidget; QHBoxLayout* layout = new QHBoxLayout(m_widget); - QPushButton* button = new QPushButton; + RichTextPushButton* button = new RichTextPushButton; // button->setPopupMode(QToolButton::InstantPopup); - button->setText(i18n("Solve")); + button->setHtml(i18n("Solve")); if(!solution->title().isEmpty()) - button->setText(i18n("Solve: %1", solution->title())); + button->setHtml(i18n("Solve: %1", solution->title())); QMenu* menu = new QMenu; menu->setFocusPolicy(Qt::NoFocus); foreach(IAssistantAction::Ptr action, solution->actions()) { menu->addAction(action->toKAction()); kDebug() << "adding action" << action->description(); } button->setMenu(menu); layout->addWidget(button); layout->setAlignment(button, Qt::AlignLeft); m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); } } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; modifyHtml() += "

"; modifyHtml() += i18n("Problem in %1:
", m_problem->sourceString()); modifyHtml() += Qt::escape(m_problem->description()); modifyHtml() += "
"; modifyHtml() += "" + m_problem->explanation() + ""; modifyHtml() += "

"; return currentHtml(); } diff --git a/language/interfaces/ilanguagesupport.h b/language/interfaces/ilanguagesupport.h index db370392b9..9642f5fc83 100644 --- a/language/interfaces/ilanguagesupport.h +++ b/language/interfaces/ilanguagesupport.h @@ -1,96 +1,97 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef ILANGUAGESUPPORT_H #define ILANGUAGESUPPORT_H #include #include "../editor/simplerange.h" #include "../languageexport.h" namespace KDevelop { class ParseJob; class ILanguage; class TopDUContext; class DocumentRange; class SimpleCursor; class SimpleRange; class ICodeHighlighting; class DocumentChangeTracker; class KDEVPLATFORMLANGUAGE_EXPORT ILanguageSupport { public: virtual ~ILanguageSupport() {} /** @return the name of the language.*/ virtual QString name() const = 0; /** @return the parse job that is used by background parser to parse given @p url.*/ virtual ParseJob *createParseJob(const KUrl &url) = 0; /** @return the language for this support.*/ virtual ILanguage *language(); /** * Only important for languages that can parse multiple different versions of a file, like C++ due to the preprocessor. * The default-implementation for other languages is "return DUChain::chainForDocument(url);" * - * @param allowProxyContext Whether proxy-contexts are allowed to be returned. In C++, a proxy-contexts has no direct content. - * It mainly just imports an actual content-context, and represents multiple different versions - * of the same content in the eyes of the preprocessor. Also, a proxy-context may contain the problem- - * descriptions of preprocessor problems, so it should be used by anything that is interested in those - * problems. + * @param proxyContext Whether the returned context should be a proxy-contexts. In C++, a proxy-contexts has no direct content. + * It mainly just imports an actual content-context, and it holds all the imports. It can also represent + * multiple different versions of the same content in the eyes of the preprocessor. Also, a proxy-context may contain the problem- + * descriptions of preprocessor problems. + * The proxy-context should be preferred whenever the problem-list is required, or for deciding whether a document needs to be updated + * (only the proxy-context knows about all the dependencies, since it contains most of the imports) * * @warning The DUChain must be locked before calling this, @see KDevelop::DUChainReadLocker * * @return the standard context used by this language for the given @param url. * */ - virtual TopDUContext *standardContext(const KUrl& url, bool allowProxyContext = false); + virtual TopDUContext *standardContext(const KUrl& url, bool proxyContext = false); /** * Should return a code-highlighting instance for this language, or zero. */ virtual ICodeHighlighting* codeHighlighting() const; /** * Should return a document change-tracker for this language that tracks the changes in the given document * */ virtual DocumentChangeTracker* createChangeTrackerForDocument(KTextEditor::Document* document) const; /** * The following functions are used to allow navigation-features, tooltips, etc. for non-duchain language objects. * In C++, they are used to allow highlighting and navigation of macro-uses. * */ /**Should return the local range within the given url that belongs to the *special language-object that contains @param position, or (KUrl(), SimpleRange:invalid()) */ virtual SimpleRange specialLanguageObjectRange(const KUrl& url, const SimpleCursor& position); /**Should return the source-range and source-document that the *special language-object that contains @param position refers to, or SimpleRange:invalid(). */ virtual QPair specialLanguageObjectJumpCursor(const KUrl& url, const SimpleCursor& position); /**Should return a navigation-widget for the *special language-object that contains @param position refers, or 0. */ virtual QWidget* specialLanguageObjectNavigationWidget(const KUrl& url, const SimpleCursor& position); }; } Q_DECLARE_INTERFACE( KDevelop::ILanguageSupport, "org.kdevelop.ILanguageSupport") #endif diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp index d7c9040ced..317e3eef0b 100644 --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -1,92 +1,102 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "outputmodel.h" #include #include #include namespace KDevelop { struct OutputModelPrivate { QTimer* timer; QVector pending; - static const int MAX_SIZE; + static const int MAX_SIZE_PENDING; static const int INTERVAL_MS; + static const int MAX_SIZE; }; -const int OutputModelPrivate::MAX_SIZE=10000; +const int OutputModelPrivate::MAX_SIZE_PENDING=10000; const int OutputModelPrivate::INTERVAL_MS=50; +const int OutputModelPrivate::MAX_SIZE=50000; + OutputModel::OutputModel( QObject* parent ) : QStandardItemModel( parent ), d(new OutputModelPrivate) { d->timer = new QTimer(this); d->timer->setInterval(OutputModelPrivate::INTERVAL_MS); d->timer->setSingleShot(true); - d->pending.reserve(OutputModelPrivate::MAX_SIZE); + d->pending.reserve(OutputModelPrivate::MAX_SIZE_PENDING); connect(d->timer, SIGNAL(timeout()), SLOT(addPending())); } OutputModel::~OutputModel() { addPending(); delete d; } void OutputModel::appendLine( const QString& line ) { QStandardItem* item = new QStandardItem( line ); item->setFont( KGlobalSettings::fixedFont() ); d->pending.append(item); - if(d->pending.size()pending.size()timer->start(); else addPending(); } void OutputModel::appendLines( const QStringList& lines) { Q_FOREACH( const QString& s, lines ) { appendLine( s ); } } void OutputModel::addPending() { - if(!d->pending.isEmpty()) + if(!d->pending.isEmpty()) { + const int aboutToAdd = d->pending.size(); + if (aboutToAdd + invisibleRootItem()->rowCount() > OutputModelPrivate::MAX_SIZE) { + // https://bugs.kde.org/show_bug.cgi?id=263050 + // make sure we don't add too many items + invisibleRootItem()->removeRows(0, aboutToAdd + invisibleRootItem()->rowCount() - OutputModelPrivate::MAX_SIZE); + } invisibleRootItem()->appendRows(d->pending.toList()); - + } + d->pending.clear(); } } #include "outputmodel.moc" diff --git a/plugins/filemanager/filemanager.cpp b/plugins/filemanager/filemanager.cpp index c46d38f1a0..5783c4ef3d 100644 --- a/plugins/filemanager/filemanager.cpp +++ b/plugins/filemanager/filemanager.cpp @@ -1,182 +1,180 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * Copyright 2006 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "filemanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" #include "kdevfilemanagerplugin.h" FileManager::FileManager(KDevFileManagerPlugin *plugin, QWidget* parent) :QWidget(parent) { Q_UNUSED( plugin ); setObjectName("FileManager"); setWindowIcon(SmallIcon("system-file-manager")); setWindowTitle(i18n("Filesystem")); setWhatsThis(i18n("Filesystem Browser")); QVBoxLayout *l = new QVBoxLayout(this); l->setMargin(0); l->setSpacing(0); KFilePlacesModel* model = new KFilePlacesModel( this ); urlnav = new KUrlNavigator(model, KUrl( QDir::homePath() ), this ); connect(urlnav, SIGNAL(urlChanged(const KUrl& )), SLOT(gotoUrl(const KUrl&))); l->addWidget(urlnav); dirop = new KDirOperator(QDir::homePath(), this); dirop->setView( KFile::Tree ); dirop->setupMenu( KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::NavActions | KDirOperator::ViewActions ); connect(dirop, SIGNAL(urlEntered(const KUrl&)), SLOT(updateNav(const KUrl&))); connect(dirop, SIGNAL(contextMenuAboutToShow(KFileItem,QMenu*)), SLOT(fillContextMenu(KFileItem,QMenu*))); //KDirOperator emits fileSelected() twice because both activated() and doubleClicked() emit fileClicked(). //activated() should be enough, so disconnect doubleClicked() disconnect(dirop->view(), SIGNAL(doubleClicked(const QModelIndex&)), dirop, SLOT(_k_slotDoubleClicked(const QModelIndex&))); l->addWidget(dirop); connect( dirop, SIGNAL(fileSelected(const KFileItem&)), this, SLOT(openFile(const KFileItem&)) ); setupActions(); } void FileManager::fillContextMenu(KFileItem item, QMenu* menu) { foreach(QAction* a, contextActions){ if(menu->actions().contains(a)){ menu->removeAction(a); } } contextActions.clear(); contextActions.append(menu->addSeparator()); menu->addAction(newFileAction); contextActions.append(newFileAction); - if (item.isFile()) { - KDevelop::FileContext context(item.url()); - QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); - KDevelop::ContextMenuExtension::populateMenu(menu, extensions); - QMenu* tmpMenu = new QMenu(); - KDevelop::ContextMenuExtension::populateMenu(tmpMenu, extensions); - contextActions.append(tmpMenu->actions()); - delete tmpMenu; - } + KDevelop::FileContext context(item.url()); + QList extensions = KDevelop::ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); + KDevelop::ContextMenuExtension::populateMenu(menu, extensions); + QMenu* tmpMenu = new QMenu(); + KDevelop::ContextMenuExtension::populateMenu(tmpMenu, extensions); + contextActions.append(tmpMenu->actions()); + delete tmpMenu; } void FileManager::openFile(const KFileItem& file) { KDevelop::IOpenWith::openFiles(KUrl::List() << file.url()); } -void FileManager::gotoUrl( const KUrl& url ) +void FileManager::gotoUrl( const KUrl& url ) { dirop->setUrl( url, true ); } void FileManager::updateNav( const KUrl& url ) { urlnav->setLocationUrl( url ); } void FileManager::setupActions() { KAction* action = new KAction(this); action->setShortcutContext(Qt::WidgetWithChildrenShortcut); action->setText(i18n("Current Document Directory")); action->setIcon(KIcon("dirsync")); connect(action, SIGNAL(triggered(bool)), this, SLOT(syncCurrentDocumentDirectory())); tbActions << (dirop->actionCollection()->action("back")); tbActions << (dirop->actionCollection()->action("up")); tbActions << (dirop->actionCollection()->action("home")); tbActions << (dirop->actionCollection()->action("forward")); tbActions << (dirop->actionCollection()->action("reload")); tbActions << action; tbActions << (dirop->actionCollection()->action("sorting menu")); tbActions << (dirop->actionCollection()->action("show hidden")); newFileAction = new KAction(this); newFileAction->setText(i18n("New File...")); newFileAction->setIcon(KIcon("document-new")); connect(newFileAction, SIGNAL(triggered()), this, SLOT(createNewFile())); } void FileManager::createNewFile() { KParts::MainWindow *activeMainWindow = KDevelop::ICore::self()->uiController()->activeMainWindow(); //TODO: adymo: use KNameAndUrlInputDialog here once we depend on KDE 4.5 bool ok = false; QString fileName = KInputDialog::getText(i18n("Create New File"), i18n("Filename:"), "", &ok, activeMainWindow); if (!ok) return; KTemporaryFile tmpFile; if (!tmpFile.open()) { kError() << "Couldn't create temp file!"; return; } KUrl destUrl = dirop->url(); destUrl.addPath(fileName); if (KIO::NetAccess::file_copy(KUrl(tmpFile.fileName()), destUrl)) KDevelop::ICore::self()->documentController()->openDocument( destUrl ); else KMessageBox::error(activeMainWindow, i18n("Unable to create file '%1'").arg(fileName)); } void FileManager::syncCurrentDocumentDirectory() { - if( KDevelop::IDocument* activeDoc = + if( KDevelop::IDocument* activeDoc = KDevelop::ICore::self()->documentController()->activeDocument() ) updateNav( activeDoc->url().upUrl() ); } QList FileManager::toolBarActions() const { return tbActions; } #include "filemanager.moc" diff --git a/plugins/grepview/grepviewplugin.cpp b/plugins/grepview/grepviewplugin.cpp index 9d83d25bf4..dc4c707518 100644 --- a/plugins/grepview/grepviewplugin.cpp +++ b/plugins/grepview/grepviewplugin.cpp @@ -1,203 +1,214 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Benjamin Port * * Copyright 2010 Julien Desgats * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepviewplugin.h" #include "grepdialog.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "grepjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(GrepViewFactory, registerPlugin(); ) K_EXPORT_PLUGIN(GrepViewFactory(KAboutData("kdevgrepview","kdevgrepview", ki18n("Find/Replace In Files"), "0.1", ki18n("Support for running grep over a list of files"), KAboutData::License_GPL))) GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( GrepViewFactory::componentData(), parent ), m_currentJob(0) { setXMLFile("kdevgrepview.rc"); KAction *action = actionCollection()->addAction("edit_grep"); action->setText(i18n("Find/replace in Fi&les...")); action->setShortcut( i18n("Ctrl+Alt+f") ); connect(action, SIGNAL(triggered(bool)), this, SLOT(showDialogFromMenu())); action->setToolTip( i18n("Search for expressions over several files") ); action->setWhatsThis( i18n("Find/Replace in files

" "Opens the 'Find/Replace in files' dialog. There you " "can enter a regular expression which is then " "searched for within all files in the directories " "you specify. Matches will be displayed, you " "can switch to a match directly. You can also do replacement.

") ); action->setIcon(KIcon("edit-find")); // instantiate delegate, it's supposed to be deleted via QObject inheritance new GrepOutputDelegate(this); } GrepViewPlugin::~GrepViewPlugin() { } KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); // verify if there is only one folder selected if ((items.count() == 1) && (items.first()->folder())) { KAction* action = new KAction( i18n( "Find and replace in this folder" ), this ); action->setIcon(KIcon("edit-find")); m_contextMenuDirectory = items.at(0)->folder()->url().toLocalFile(); connect( action, SIGNAL(triggered()), this, SLOT(showDialogFromProject())); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); if ( econtext->view()->selection() ) { QAction* action = new QAction(KIcon("edit-find"), i18n("&Find/Replace in Files"), this); connect(action, SIGNAL(triggered(bool)), this, SLOT(showDialogFromMenu())); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } + if(context->type() == KDevelop::Context::FileContext) { + KDevelop::FileContext *fcontext = dynamic_cast(context); + KMimeType::Ptr mimetype = KMimeType::findByUrl( fcontext->urls().first() ); + if(mimetype->is("inode/directory")) { + KAction* action = new KAction( i18n( "Find and replace in this folder" ), this ); + action->setIcon(KIcon("edit-find")); + m_contextMenuDirectory = fcontext->urls().first().toLocalFile(); + connect( action, SIGNAL(triggered()), this, SLOT(showDialogFromProject())); + extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); + } + } return extension; } void GrepViewPlugin::showDialog(bool setLastUsed) { GrepDialog* dlg = new GrepDialog( this, core()->uiController()->activeMainWindow(), setLastUsed ); KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if(!setLastUsed) { QString pattern; if( doc ) { KTextEditor::Range range = doc->textSelection(); if( range.isValid() ) { pattern = doc->textDocument()->text( range ); } if( pattern.isEmpty() ) { pattern = doc->textWord(); } } // Before anything, this removes line feeds from the // beginning and the end. int len = pattern.length(); if (len > 0 && pattern[0] == '\n') { pattern.remove(0, 1); len--; } if (len > 0 && pattern[len-1] == '\n') pattern.truncate(len-1); if (!pattern.isEmpty()) { dlg->setPattern( pattern ); } dlg->enableButtonOk( !pattern.isEmpty() ); } if (!m_directory.isEmpty() && QFileInfo(m_directory).isDir()) { dlg->setDirectory(m_directory); } else { KUrl currentUrl; KDevelop::IDocument *document = core()->documentController()->activeDocument(); dlg->setEnableProjectBox(false); if( document ) { currentUrl = document->url(); } if( currentUrl.isValid() ) { KDevelop::IProject *proj = core()->projectController()->findProjectForUrl( currentUrl ); if( proj && proj->folder().isLocalFile() ) { dlg->setEnableProjectBox(! proj->files().isEmpty() ); if (!m_directory.startsWith(proj->folder().toLocalFile())) { dlg->setDirectory( proj->folder().toLocalFile() ); } } } } dlg->show(); } void GrepViewPlugin::showDialogFromMenu() { showDialog(); } void GrepViewPlugin::showDialogFromProject() { rememberSearchDirectory(m_contextMenuDirectory); showDialog(); } void GrepViewPlugin::rememberSearchDirectory(QString const & directory) { m_directory = directory; } GrepJob* GrepViewPlugin::grepJob() { if(m_currentJob != 0) { m_currentJob->kill(); } m_currentJob = new GrepJob(); connect(m_currentJob, SIGNAL(finished(KJob*)), this, SLOT(jobFinished(KJob*))); return m_currentJob; } void GrepViewPlugin::jobFinished(KJob* job) { if(job == m_currentJob) { m_currentJob = 0; } } #include "grepviewplugin.moc" diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index 0b3672a417..70b16093dd 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,206 +1,207 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "openwithplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - using namespace KDevelop; K_PLUGIN_FACTORY(KDevOpenWithFactory, registerPlugin(); ) K_EXPORT_PLUGIN(KDevOpenWithFactory(KAboutData("kdevopenwith","kdevopenwith", ki18n("Open With"), "0.1", ki18n("Open files with external applications."), KAboutData::License_GPL))) OpenWithPlugin::OpenWithPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( KDevOpenWithFactory::componentData(), parent ), m_actionMap( 0 ) { // setXMLFile( "kdevopenwithui.rc" ); KDEV_USE_EXTENSION_INTERFACE( IOpenWith ) } OpenWithPlugin::~OpenWithPlugin() { } KDevelop::ContextMenuExtension OpenWithPlugin::contextMenuExtension ( KDevelop::Context* context ) { if( m_actionMap ) { delete m_actionMap; m_actionMap = 0; } m_urls = QList(); FileContext* filectx = dynamic_cast( context ); ProjectItemContext* projctx = dynamic_cast( context ); if( filectx && filectx->urls().count() > 0 ) { m_urls = filectx->urls(); } else if ( projctx && projctx->items().count() > 0 ) { foreach( ProjectBaseItem* item, projctx->items() ) { if( item->file() ) { m_urls << item->file()->url(); } } } if( !m_urls.isEmpty() ) { m_actionMap = new QSignalMapper( this ); connect( m_actionMap, SIGNAL(mapped(const QString&)), SLOT(open(const QString&)) ); - + // Ok, lets fetch the mimetype for the !!first!! url and the relevant services // TODO: Think about possible alternatives to using the mimetype of the first url. KMimeType::Ptr mimetype = KMimeType::findByUrl( m_urls.first() ); - m_mimeType = mimetype->name(); - KService::List apps = KMimeTypeTrader::self()->query( m_mimeType ); - KService::Ptr preferredapp = KMimeTypeTrader::self()->preferredService( m_mimeType ); - KService::List parts = KMimeTypeTrader::self()->query( m_mimeType, "KParts/ReadOnlyPart" ); - KService::Ptr preferredpart = KMimeTypeTrader::self()->preferredService( m_mimeType, - "KParts/ReadOnlyPart" ); - - // Now setup a menu with actions for each part and app - KMenu* menu = new KMenu( i18n("Open With" ) ); - menu->setIcon( SmallIcon( "document-open" ) ); - - menu->addActions( actionsForServices( parts, preferredpart ) ); - menu->addActions( actionsForServices( apps, preferredapp ) ); - - KAction* openAction = new KAction( i18n( "Open" ), this ); - openAction->setIcon( SmallIcon( "document-open" ) ); - connect( openAction, SIGNAL( triggered() ), SLOT( openDefault() ) ); - - KDevelop::ContextMenuExtension ext; - ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); - ext.addAction( KDevelop::ContextMenuExtension::FileGroup, menu->menuAction() ); - return ext; + if(!mimetype->is("inode/directory")){ + m_mimeType = mimetype->name(); + KService::List apps = KMimeTypeTrader::self()->query( m_mimeType ); + KService::Ptr preferredapp = KMimeTypeTrader::self()->preferredService( m_mimeType ); + KService::List parts = KMimeTypeTrader::self()->query( m_mimeType, "KParts/ReadOnlyPart" ); + KService::Ptr preferredpart = KMimeTypeTrader::self()->preferredService( m_mimeType, + "KParts/ReadOnlyPart" ); + + // Now setup a menu with actions for each part and app + KMenu* menu = new KMenu( i18n("Open With" ) ); + menu->setIcon( SmallIcon( "document-open" ) ); + + menu->addActions( actionsForServices( parts, preferredpart ) ); + menu->addActions( actionsForServices( apps, preferredapp ) ); + + KAction* openAction = new KAction( i18n( "Open" ), this ); + openAction->setIcon( SmallIcon( "document-open" ) ); + connect( openAction, SIGNAL( triggered() ), SLOT( openDefault() ) ); + + KDevelop::ContextMenuExtension ext; + ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); + ext.addAction( KDevelop::ContextMenuExtension::FileGroup, menu->menuAction() ); + return ext; + } } return KDevelop::IPlugin::contextMenuExtension ( context ); } QList< QAction* > OpenWithPlugin::actionsForServices ( const KService::List& list, KService::Ptr pref ) { QList openactions; foreach( KService::Ptr svc, list ) { KAction* act = new KAction( svc->name(), this ); act->setIcon( SmallIcon( svc->icon() ) ); connect(act, SIGNAL(triggered()), m_actionMap, SLOT(map())); m_actionMap->setMapping( act, svc->storageId() ); if( svc->storageId() == pref->storageId() ) { openactions.prepend( act ); } else { openactions.append( act ); } } return openactions; } void OpenWithPlugin::openDefault() { KConfigGroup config = KGlobal::config()->group("Open With Defaults"); if (config.hasKey(m_mimeType)) { QString storageId = config.readEntry(m_mimeType, QString()); if (!storageId.isEmpty() && KService::serviceByStorageId(storageId)) { open(storageId); return; } } foreach( const KUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u ); } } void OpenWithPlugin::open ( const QString& storageid ) { KService::Ptr svc = KService::serviceByStorageId( storageid ); if( svc->isApplication() ) { KRun::run( *svc, m_urls, ICore::self()->uiController()->activeMainWindow() ); } else { QString prefName = svc->desktopEntryName(); if( svc->serviceTypes().contains( "KTextEditor/Document" ) ) { // If the user chose a KTE part, lets make sure we're creating a TextDocument instead of // a PartDocument by passing no preferredpart to the documentcontroller // TODO: Solve this rather inside DocumentController prefName = ""; } foreach( const KUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u, prefName ); } } KConfigGroup config = KGlobal::config()->group("Open With Defaults"); if (storageid != config.readEntry(m_mimeType, QString())) { int setDefault = KMessageBox::questionYesNo( qApp->activeWindow(), i18n("Do you want to open %1 files by default with %2?", m_mimeType, svc->name() ), i18n("Set as default?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString("OpenWith-%1").arg(m_mimeType) ); if (setDefault == KMessageBox::Yes) { config.writeEntry(m_mimeType, storageid); } } } void OpenWithPlugin::openFilesInternal( const KUrl::List& files ) { if (files.isEmpty()) { return; } m_urls = files; m_mimeType = KMimeType::findByUrl( m_urls.first() )->name(); openDefault(); } diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp index ea00cf3da3..4ce05b2069 100644 --- a/shell/assistantpopup.cpp +++ b/shell/assistantpopup.cpp @@ -1,81 +1,90 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "assistantpopup.h" #include -#include #include #include #include #include +#include const int SPACING_FROM_PARENT_BOTTOM = 5; void AssistantPopup::updateActions() { QPalette palette = QApplication::palette(); palette.setBrush(QPalette::Background, palette.toolTipBase()); setPalette(palette); m_assistantActions = m_assistant->actions(); + bool haveTitle = false; if (!m_assistant->title().isEmpty()) { + haveTitle = true; QLabel* title = new QLabel("" + m_assistant->title() + ":"); title->setTextFormat(Qt::RichText); addWidget(title); } + ///@todo Add some intelligent layouting to make sure the widget doesn't become too wide foreach(KDevelop::IAssistantAction::Ptr action, m_assistantActions) + { + if(haveTitle || action != m_assistantActions.first()) + addSeparator(); addWidget(widgetForAction(action)); + } addSeparator(); addWidget(widgetForAction(KDevelop::IAssistantAction::Ptr())); resize(sizeHint()); move((parentWidget()->width() - width())/2, parentWidget()->height() - height() - SPACING_FROM_PARENT_BOTTOM); } AssistantPopup::AssistantPopup(QWidget* parent, KDevelop::IAssistant::Ptr assistant) : QToolBar(parent), m_assistant(assistant) { Q_ASSERT(assistant); setAutoFillBackground(true); updateActions(); } QWidget* AssistantPopup::widgetForAction(KDevelop::IAssistantAction::Ptr action) { - QToolButton* button = new QToolButton; + KDevelop::RichTextToolButton* button = new KDevelop::RichTextToolButton; KAction* realAction; QString buttonText; int index = m_assistantActions.indexOf(action); if(index == -1) { realAction = new KAction(button); - buttonText = "&0 - " + i18n("Hide"); + buttonText = "0 - " + i18n("Hide"); } else { realAction = action->toKAction(); - buttonText = QString("&%1 - ").arg(index+1) + action->description(); + buttonText = QString("%1 - ").arg(index+1) + action->description(); } realAction->setParent(button); connect(realAction, SIGNAL(triggered(bool)), SLOT(executeHideAction())); button->setDefaultAction(realAction); - button->setText(buttonText); + + button->setText(QString("&%1").arg(index+1)); // Let the button care about the shortcut + button->setHtml(buttonText); return button; } void AssistantPopup::executeHideAction() { m_assistant->doHide(); } KSharedPtr< KDevelop::IAssistant > AssistantPopup::assistant() const { return m_assistant; } #include "assistantpopup.moc" diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 7af253f359..e83e2f6081 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,74 +1,78 @@ add_definitions( -DKDE_DEFAULT_DEBUG_AREA=9508 ) ########### next target ############### set(kdevplatformutil_LIB_SRCS + richtextpushbutton.cpp + richtexttoolbutton.cpp kdevstringhandler.cpp focusedtreeview.cpp processlinemaker.cpp commandexecutor.cpp environmentconfigurebutton.cpp environmentselectionwidget.cpp environmentgrouplist.cpp activetooltip.cpp executecompositejob.cpp ) set (kdevplatformutil_LIB_UI runoptions.ui ) add_subdirectory(duchainify) add_subdirectory(tests) kde4_add_ui_files(kdevplatformutil_LIB_SRCS ${kdevplatformutil_LIB_US}) kde4_add_library(kdevplatformutil SHARED ${kdevplatformutil_LIB_SRCS}) target_link_libraries(kdevplatformutil ${KDE4_KDEUI_LIBS} ${KDE4_KCMUTILS_LIBRARY} kdevplatforminterfaces ) # Might want to add kdevplatform* when they're exported targets target_link_libraries(kdevplatformutil LINK_INTERFACE_LIBRARIES ${KDE4_KDEUI_LIBS}) set_target_properties(kdevplatformutil PROPERTIES VERSION ${KDEVPLATFORM_LIB_VERSION} SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install(TARGETS kdevplatformutil EXPORT KDevPlatformTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( FILES + richtextpushbutton.h + richtexttoolbutton.h kdevstringhandler.h ksharedobject.h focusedtreeview.h activetooltip.h processlinemaker.h commandexecutor.h utilexport.h environmentconfigurebutton.h environmentselectionwidget.h environmentgrouplist.h pushvalue.h kdevvarlengtharray.h embeddedfreetree.h executecompositejob.h convenientfreelist.h spinlock.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util COMPONENT Devel) install( FILES google/dense_hash_map google/dense_hash_set google/sparse_hash_map google/sparse_hash_set google/sparsetable google/type_traits.h google/hash_fun.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util/google COMPONENT Devel) install( FILES google/sparsehash/densehashtable.h google/sparsehash/sparseconfig.h google/sparsehash/sparseconfig_windows.h google/sparsehash/sparsehashtable.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util/google/sparsehash COMPONENT Devel) diff --git a/util/richtextpushbutton.cpp b/util/richtextpushbutton.cpp new file mode 100644 index 0000000000..1d6bbe863b --- /dev/null +++ b/util/richtextpushbutton.cpp @@ -0,0 +1,128 @@ +/* + Copyright 2010 Unknown Author (Qt Centre) + Copyright 2010 David Nolden + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "richtexttoolbutton.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; + +RichTextToolButton::RichTextToolButton(QWidget *parent) : + QToolButton(parent) +{ +} + +void RichTextToolButton::setHtml(const QString &text) +{ + htmlText = text; + isRichText = true; + + QPalette palette; + palette.setBrush(QPalette::ButtonText, Qt::transparent); + setPalette(palette); +} + +void RichTextToolButton::setText(const QString &text) +{ + isRichText = false; + QToolButton::setText(text); +} + + +QString RichTextToolButton::text() const +{ + if (isRichText) { + QTextDocument richText; + richText.setHtml(htmlText); + return richText.toPlainText(); + } else + return QToolButton::text(); +} + +QSize RichTextToolButton::sizeHint() const +{ + if(!isRichText) { + return QToolButton::sizeHint(); + } else{ + QTextDocument richTextLabel; + richTextLabel.setHtml(htmlText); + return richTextLabel.size().toSize(); + } +} + +void RichTextToolButton::paintEvent(QPaintEvent *event) +{ + if (isRichText) { + QStylePainter p(this); + + QRect buttonRect = rect(); + QPoint point; + + QTextDocument richTextLabel; + richTextLabel.setHtml(htmlText); + + QPixmap richTextPixmap(richTextLabel.size().width(), richTextLabel.size().height()); + richTextPixmap.fill(Qt::transparent); + QPainter richTextPainter(&richTextPixmap); + richTextLabel.drawContents(&richTextPainter, richTextPixmap.rect()); + + if (!icon().isNull()) + point = QPoint(buttonRect.x() + buttonRect.width() / 2 + iconSize().width() / 2 + 2, buttonRect.y() + buttonRect.height() / 2); + else + point = QPoint(buttonRect.x() + buttonRect.width() / 2 - 1, buttonRect.y() + buttonRect.height() / 2); + + buttonRect.translate(point.x() - richTextPixmap.width() / 2, point.y() - richTextPixmap.height() / 2); + + p.drawControl(QStyle::CE_PushButton, getStyleOption()); + p.drawPixmap(buttonRect.left(), buttonRect.top(), richTextPixmap.width(), richTextPixmap.height(),richTextPixmap); + } else + QToolButton::paintEvent(event); +} + +QStyleOptionButton RichTextToolButton::getStyleOption() const +{ + QStyleOptionButton opt; + + opt.initFrom(this); + opt.features = QStyleOptionButton::None; + if (menu()) + opt.features |= QStyleOptionButton::HasMenu; + if (isDown() || (menu() && menu()->isVisible())) + opt.state |= QStyle::State_Sunken; + if (isChecked()) + opt.state |= QStyle::State_On; + if (!isDown()) + { + if(opt.state & QStyle::State_MouseOver) + opt.state |= QStyle::State_Raised; + else + opt.features |= QStyleOptionButton::Flat; + } + opt.text = text(); + opt.icon = icon(); + opt.iconSize = iconSize(); + return opt; +} diff --git a/util/richtextpushbutton.h b/util/richtextpushbutton.h new file mode 100644 index 0000000000..6a91ca0c70 --- /dev/null +++ b/util/richtextpushbutton.h @@ -0,0 +1,57 @@ +/* + Copyright 2010 Unknown Author (Qt Centre) + Copyright 2010 David Nolden + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef RICHTEXTPUSHBUTTON_H +#define RICHTEXTPUSHBUTTON_H + +#include +#include +#include +#include "utilexport.h" + +namespace KDevelop { +class KDEVPLATFORMUTIL_EXPORT RichTextPushButton : public QPushButton +{ +Q_OBJECT +public: + explicit RichTextPushButton(QWidget *parent = 0); + + void setHtml(const QString &text); + void setText(const QString &text); + QString text() const; + + virtual QSize sizeHint() const; +signals: + +public slots: + +protected: + void paintEvent(QPaintEvent *); + +private: + QString htmlText; + bool isRichText; + + QStyleOptionButton getStyleOption() const; +}; + +} + +#endif // RICHTEXTPUSHBUTTON_H diff --git a/util/richtexttoolbutton.cpp b/util/richtexttoolbutton.cpp new file mode 100644 index 0000000000..fa59879346 --- /dev/null +++ b/util/richtexttoolbutton.cpp @@ -0,0 +1,128 @@ +/* + Copyright 2010 Unknown Author (Qt Centre) + Copyright 2010 David Nolden + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "richtextpushbutton.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; + +RichTextPushButton::RichTextPushButton(QWidget *parent) : + QPushButton(parent) +{ +} + +void RichTextPushButton::setHtml(const QString &text) +{ + htmlText = text; + isRichText = true; + + QPalette palette; + palette.setBrush(QPalette::ButtonText, Qt::transparent); + setPalette(palette); +} + +void RichTextPushButton::setText(const QString &text) +{ + isRichText = false; + QPushButton::setText(text); +} + + +QString RichTextPushButton::text() const +{ + if (isRichText) { + QTextDocument richText; + richText.setHtml(htmlText); + return richText.toPlainText(); + } else + return QPushButton::text(); +} + +QSize RichTextPushButton::sizeHint() const +{ + if(!isRichText) { + return QPushButton::sizeHint(); + } else{ + QTextDocument richTextLabel; + richTextLabel.setHtml(htmlText); + return richTextLabel.size().toSize(); + } +} + +void RichTextPushButton::paintEvent(QPaintEvent *event) +{ + if (isRichText) { + QStylePainter p(this); + + QRect buttonRect = rect(); + QPoint point; + + QTextDocument richTextLabel; + richTextLabel.setHtml(htmlText); + + QPixmap richTextPixmap(richTextLabel.size().width(), richTextLabel.size().height()); + richTextPixmap.fill(Qt::transparent); + QPainter richTextPainter(&richTextPixmap); + richTextLabel.drawContents(&richTextPainter, richTextPixmap.rect()); + + if (!icon().isNull()) + point = QPoint(buttonRect.x() + buttonRect.width() / 2 + iconSize().width() / 2 + 2, buttonRect.y() + buttonRect.height() / 2); + else + point = QPoint(buttonRect.x() + buttonRect.width() / 2 - 1, buttonRect.y() + buttonRect.height() / 2); + + buttonRect.translate(point.x() - richTextPixmap.width() / 2, point.y() - richTextPixmap.height() / 2); + + p.drawControl(QStyle::CE_PushButton, getStyleOption()); + p.drawPixmap(buttonRect.left(), buttonRect.top(), richTextPixmap.width(), richTextPixmap.height(),richTextPixmap); + } else + QPushButton::paintEvent(event); +} + +QStyleOptionButton RichTextPushButton::getStyleOption() const +{ + QStyleOptionButton opt; + opt.initFrom(this); + opt.features = QStyleOptionButton::None; + if (isFlat()) + opt.features |= QStyleOptionButton::Flat; + if (menu()) + opt.features |= QStyleOptionButton::HasMenu; + if (autoDefault() || isDefault()) + opt.features |= QStyleOptionButton::AutoDefaultButton; + if (isDefault()) + opt.features |= QStyleOptionButton::DefaultButton; + if (isDown() || (menu() && menu()->isVisible())) + opt.state |= QStyle::State_Sunken; + if (isChecked()) + opt.state |= QStyle::State_On; + if (!isFlat() && !isDown()) + opt.state |= QStyle::State_Raised; + opt.text = text(); + opt.icon = icon(); + opt.iconSize = iconSize(); + return opt; +} diff --git a/util/richtexttoolbutton.h b/util/richtexttoolbutton.h new file mode 100644 index 0000000000..51ab013c5c --- /dev/null +++ b/util/richtexttoolbutton.h @@ -0,0 +1,57 @@ +/* + Copyright 2010 Unknown Author (Qt Centre) + Copyright 2010 David Nolden + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef RICHTEXTTOOLBUTTON_H +#define RICHTEXTTOOLBUTTON_H + +#include +#include +#include +#include "utilexport.h" + +namespace KDevelop { +class KDEVPLATFORMUTIL_EXPORT RichTextToolButton : public QToolButton +{ +Q_OBJECT +public: + explicit RichTextToolButton(QWidget *parent = 0); + + void setHtml(const QString &text); + void setText(const QString &text); + QString text() const; + + virtual QSize sizeHint() const; +signals: + +public slots: + +protected: + void paintEvent(QPaintEvent *); + +private: + QString htmlText; + bool isRichText; + + QStyleOptionButton getStyleOption() const; +}; + +} + +#endif // RICHTEXTPUSHBUTTON_H