diff --git a/kdevplatform/documentation/standarddocumentationview.cpp b/kdevplatform/documentation/standarddocumentationview.cpp index 65b3443a66..9a7ff9bc1d 100644 --- a/kdevplatform/documentation/standarddocumentationview.cpp +++ b/kdevplatform/documentation/standarddocumentationview.cpp @@ -1,399 +1,399 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * Copyright 2016 Igor Kushnir * * 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 "standarddocumentationview.h" #include "documentationfindwidget.h" #include "debug.h" #include #include #include #include #include #include #ifdef USE_QTWEBKIT #include #include #include #include #else #include #include #include #include #include #include #include #include #endif using namespace KDevelop; #ifndef USE_QTWEBKIT class StandardDocumentationPage : public QWebEnginePage { Q_OBJECT public: StandardDocumentationPage(QWebEngineProfile* profile, KDevelop::StandardDocumentationView* parent) : QWebEnginePage(profile, parent), m_view(parent) { } bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override { qCDebug(DOCUMENTATION) << "navigating to..." << url << type; if (type == NavigationTypeLinkClicked && m_isDelegating) { emit m_view->linkClicked(url); return false; } return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); } void setLinkDelegating(bool isDelegating) { m_isDelegating = isDelegating; } private: KDevelop::StandardDocumentationView* const m_view; bool m_isDelegating = false; }; #endif class KDevelop::StandardDocumentationViewPrivate { public: ZoomController* m_zoomController = nullptr; IDocumentation::Ptr m_doc; #ifdef USE_QTWEBKIT QWebView *m_view = nullptr; void init(StandardDocumentationView* parent) { m_view = new QWebView(parent); m_view->setContextMenuPolicy(Qt::NoContextMenu); QObject::connect(m_view, &QWebView::linkClicked, parent, &StandardDocumentationView::linkClicked); } #else QWebEngineView* m_view = nullptr; StandardDocumentationPage* m_page = nullptr; void init(StandardDocumentationView* parent) { // prevent QWebEngine (Chromium) from overriding the signal handlers of KCrash const auto chromiumFlags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"); if (!chromiumFlags.contains("disable-in-process-stack-traces")) { qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags + " --disable-in-process-stack-traces"); } // not using the shared default profile here: // prevents conflicts with qthelp scheme handler being registered onto that single default profile // due to async deletion of old pages and their CustomSchemeHandler instance auto* profile = new QWebEngineProfile(parent); m_page = new StandardDocumentationPage(profile, parent); m_view = new QWebEngineView(parent); m_view->setPage(m_page); // workaround for Qt::NoContextMenu broken with QWebEngineView, contextmenu event is always eaten // see https://bugreports.qt.io/browse/QTBUG-62345 // we have to enforce deferring of event ourselves m_view->installEventFilter(parent); } #endif }; StandardDocumentationView::StandardDocumentationView(DocumentationFindWidget* findWidget, QWidget* parent) : QWidget(parent) , d(new StandardDocumentationViewPrivate) { auto mainLayout = new QVBoxLayout(this); mainLayout->setMargin(0); setLayout(mainLayout); d->init(this); layout()->addWidget(d->m_view); findWidget->setEnabled(true); connect(findWidget, &DocumentationFindWidget::searchRequested, this, &StandardDocumentationView::search); connect(findWidget, &DocumentationFindWidget::searchDataChanged, this, &StandardDocumentationView::searchIncremental); connect(findWidget, &DocumentationFindWidget::searchFinished, this, &StandardDocumentationView::finishSearch); #ifdef USE_QTWEBKIT QFont sansSerifFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont monospaceFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); QWebSettings* s = d->m_view->settings(); s->setFontFamily(QWebSettings::StandardFont, sansSerifFont.family()); s->setFontFamily(QWebSettings::SerifFont, QStringLiteral("Serif")); s->setFontFamily(QWebSettings::SansSerifFont, sansSerifFont.family()); s->setFontFamily(QWebSettings::FixedFont, monospaceFont.family()); s->setFontSize(QWebSettings::DefaultFontSize, QFontInfo(sansSerifFont).pixelSize()); s->setFontSize(QWebSettings::DefaultFixedFontSize, QFontInfo(monospaceFont).pixelSize()); // Fixes for correct positioning. The problem looks like the following: // // 1) Some page is loaded and loadFinished() signal is emitted, // after this QWebView set right position inside page. // // 2) After loadFinished() emitting, page JS code finishes it's work and changes // font settings (size). This leads to page contents "moving" inside view widget // and as a result we have wrong position. // // Such behavior occurs for example with QtHelp pages. // // To fix the problem, first, we disable view painter updates during load to avoid content // "flickering" and also to hide font size "jumping". Secondly, we reset position inside page // after loading with using standard QWebFrame method scrollToAnchor(). connect(d->m_view, &QWebView::loadStarted, d->m_view, [this]() { d->m_view->setUpdatesEnabled(false); }); connect(d->m_view, &QWebView::loadFinished, this, [this](bool) { if (d->m_view->url().isValid()) { d->m_view->page()->mainFrame()->scrollToAnchor(d->m_view->url().fragment()); } d->m_view->setUpdatesEnabled(true); }); #endif } KDevelop::StandardDocumentationView::~StandardDocumentationView() = default; void StandardDocumentationView::search ( const QString& text, DocumentationFindWidget::FindOptions options ) { #ifdef USE_QTWEBKIT - typedef QWebPage WebkitThing; + using WebkitThing = QWebPage; #else - typedef QWebEnginePage WebkitThing; + using WebkitThing = QWebEnginePage; #endif WebkitThing::FindFlags ff = {}; if(options & DocumentationFindWidget::Previous) ff |= WebkitThing::FindBackward; if(options & DocumentationFindWidget::MatchCase) ff |= WebkitThing::FindCaseSensitively; d->m_view->page()->findText(text, ff); } void StandardDocumentationView::searchIncremental(const QString& text, DocumentationFindWidget::FindOptions options) { #ifdef USE_QTWEBKIT - typedef QWebPage WebkitThing; + using WebkitThing = QWebPage; #else - typedef QWebEnginePage WebkitThing; + using WebkitThing = QWebEnginePage; #endif WebkitThing::FindFlags findFlags; if (options & DocumentationFindWidget::MatchCase) findFlags |= WebkitThing::FindCaseSensitively; // calling with changed text with added or removed chars at end will result in current // selection kept, if also matching new text // behaviour on changed case sensitivity though is advancing to next match even if current // would be still matching. as there is no control about currently shown match, nothing // we can do about it. thankfully case sensitivity does not happen too often, so should // not be too grave UX // at least with webengine 5.9.1 there is a bug when switching from no-casesensitivy to // casesensitivity, that global matches are not updated and the ones with non-matching casing // still active. no workaround so far. d->m_view->page()->findText(text, findFlags); } void StandardDocumentationView::finishSearch() { // passing emptry string to reset search, as told in API docs d->m_view->page()->findText(QString()); } void StandardDocumentationView::initZoom(const QString& configSubGroup) { Q_ASSERT_X(!d->m_zoomController, "StandardDocumentationView::initZoom", "Can not initZoom a second time."); const KConfigGroup outerGroup(KSharedConfig::openConfig(), QStringLiteral("Documentation View")); const KConfigGroup configGroup(&outerGroup, configSubGroup); d->m_zoomController = new ZoomController(configGroup, this); connect(d->m_zoomController, &ZoomController::factorChanged, this, &StandardDocumentationView::updateZoomFactor); updateZoomFactor(d->m_zoomController->factor()); } void StandardDocumentationView::setDocumentation(const IDocumentation::Ptr& doc) { if(d->m_doc) disconnect(d->m_doc.data()); d->m_doc = doc; update(); if(d->m_doc) connect(d->m_doc.data(), &IDocumentation::descriptionChanged, this, &StandardDocumentationView::update); } void StandardDocumentationView::update() { if(d->m_doc) { setHtml(d->m_doc->description()); } else qCDebug(DOCUMENTATION) << "calling StandardDocumentationView::update() on an uninitialized view"; } void KDevelop::StandardDocumentationView::setOverrideCss(const QUrl& url) { #ifdef USE_QTWEBKIT d->m_view->settings()->setUserStyleSheetUrl(url); #else d->m_view->page()->runJavaScript(QLatin1String( "var link = document.createElement( 'link' );" "link.href = '") + url.toString() + QLatin1String("';" "link.type = 'text/css';" "link.rel = 'stylesheet';" "link.media = 'screen,print';" "document.getElementsByTagName( 'head' )[0].appendChild( link );") ); #endif } void KDevelop::StandardDocumentationView::load(const QUrl& url) { #ifdef USE_QTWEBKIT d->m_view->load(url); #else d->m_view->page()->load(url); #endif } void KDevelop::StandardDocumentationView::setHtml(const QString& html) { #ifdef USE_QTWEBKIT d->m_view->setHtml(html); #else d->m_view->page()->setHtml(html); #endif } #ifndef USE_QTWEBKIT class CustomSchemeHandler : public QWebEngineUrlSchemeHandler { Q_OBJECT public: explicit CustomSchemeHandler(QNetworkAccessManager* nam, QObject *parent = nullptr) : QWebEngineUrlSchemeHandler(parent), m_nam(nam) {} void requestStarted(QWebEngineUrlRequestJob *job) override { const QUrl url = job->requestUrl(); auto reply = m_nam->get(QNetworkRequest(url)); job->reply("text/html", reply); } private: QNetworkAccessManager* m_nam; }; #endif void KDevelop::StandardDocumentationView::setNetworkAccessManager(QNetworkAccessManager* manager) { #ifdef USE_QTWEBKIT d->m_view->page()->setNetworkAccessManager(manager); #else d->m_view->page()->profile()->installUrlSchemeHandler("qthelp", new CustomSchemeHandler(manager, this)); #endif } void KDevelop::StandardDocumentationView::setDelegateLinks(bool delegate) { #ifdef USE_QTWEBKIT d->m_view->page()->setLinkDelegationPolicy(delegate ? QWebPage::DelegateAllLinks : QWebPage::DontDelegateLinks); #else d->m_page->setLinkDelegating(delegate); #endif } QMenu* StandardDocumentationView::createStandardContextMenu() { auto menu = new QMenu(this); #ifdef USE_QTWEBKIT - typedef QWebPage WebkitThing; + using WebkitThing = QWebPage; #else - typedef QWebEnginePage WebkitThing; + using WebkitThing = QWebEnginePage; #endif auto copyAction = d->m_view->pageAction(WebkitThing::Copy); if (copyAction) { copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); menu->addAction(copyAction); } return menu; } bool StandardDocumentationView::eventFilter(QObject* object, QEvent* event) { #ifndef USE_QTWEBKIT if (object == d->m_view) { // help QWebEngineView properly behave like expected as if Qt::NoContextMenu was set if (event->type() == QEvent::ContextMenu) { event->ignore(); return true; } } #endif return QWidget::eventFilter(object, event); } void StandardDocumentationView::contextMenuEvent(QContextMenuEvent* event) { auto menu = createStandardContextMenu(); if (menu->isEmpty()) { delete menu; return; } menu->setAttribute(Qt::WA_DeleteOnClose); menu->exec(event->globalPos()); } void StandardDocumentationView::updateZoomFactor(double zoomFactor) { d->m_view->setZoomFactor(zoomFactor); } void StandardDocumentationView::keyPressEvent(QKeyEvent* event) { if (d->m_zoomController && d->m_zoomController->handleKeyPressEvent(event)) { return; } QWidget::keyPressEvent(event); } void StandardDocumentationView::wheelEvent(QWheelEvent* event) { if (d->m_zoomController && d->m_zoomController->handleWheelEvent(event)) { return; } QWidget::wheelEvent(event); } #ifndef USE_QTWEBKIT #include "standarddocumentationview.moc" #endif diff --git a/kdevplatform/interfaces/iassistant.h b/kdevplatform/interfaces/iassistant.h index 35f6eb781c..22aa7dd122 100644 --- a/kdevplatform/interfaces/iassistant.h +++ b/kdevplatform/interfaces/iassistant.h @@ -1,150 +1,150 @@ /* 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 KDEVPLATFORM_IASSISTANT_H #define KDEVPLATFORM_IASSISTANT_H #include #include #include "interfacesexport.h" #include class QAction; 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 QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; ~IAssistantAction() override; ///Creates a QAction that represents this exact assistant action. ///The caller owns the action, and is responsible for deleting it. virtual QAction* toQAction(QObject* parent = nullptr) 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. * * NOTE: Implementations should properly emit executed(this) after being executed. */ virtual void execute() = 0; Q_SIGNALS: /** * Gets emitted when this action was executed. */ void executed(IAssistantAction* action); }; /** * A fake action that only shows a label. */ class KDEVPLATFORMINTERFACES_EXPORT AssistantLabelAction : public IAssistantAction { Q_OBJECT public: /** * @p description The label to show. */ explicit AssistantLabelAction(const QString& description); /** * @return the label contents. */ QString description() const override; /** * The label cannot be executed. */ void execute() override; /** * No action is returned. */ QAction* toQAction(QObject* parent = nullptr) const override; 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(); ~IAssistant() override; - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; ///Returns the stored list of actions QList actions() const; ///Implement this and have it create the actions for your assistant. ///It will only be called if the assistant is displayed, which saves ///memory compared to creating the actions right away. ///Default implementation does nothing. virtual void createActions(); ///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(const 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 // KDEVPLATFORM_IASSISTANT_H diff --git a/kdevplatform/interfaces/iproblem.h b/kdevplatform/interfaces/iproblem.h index a32e62e6db..1e4ce96767 100644 --- a/kdevplatform/interfaces/iproblem.h +++ b/kdevplatform/interfaces/iproblem.h @@ -1,149 +1,149 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 IPROBLEM_H #define IPROBLEM_H #include #include #include #include "interfacesexport.h" namespace KDevelop { class IAssistant; class DocumentRange; /// Interface for the Problem classes class KDEVPLATFORMINTERFACES_EXPORT IProblem : public QSharedData { public: - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; /// The source of the problem. That is which tool / which part found this problem. enum Source { Unknown, Disk, Preprocessor, Lexer, Parser, DUChainBuilder, SemanticAnalysis, ToDo, Plugin /// The source is a problem checker plugin }; /// Severity of the problem. That is, how serious is the found problem. enum Severity { NoSeverity = 0, Error = 1, Warning = 2, Hint = 4 }; Q_DECLARE_FLAGS(Severities, Severity) /// Final location mode of the problem. Used during highlighting. enum FinalLocationMode { /// Location range used "As Is" Range = 0, /// Location range used to highlight whole line. /// /// Mode applied only if location range is wholly contained within one line WholeLine, /// Location range used to highlight only trimmed part of the line. /// For example for the line: " int x = 0; \t" /// only "int x = 0;" will be highlighted. /// /// Mode applied only if location range is wholly contained within one line TrimmedLine }; IProblem(); virtual ~IProblem(); /// Returns the source of the problem virtual Source source() const = 0; /// Sets the source of the problem virtual void setSource(Source source) = 0; /// Returns a string containing the source of the problem virtual QString sourceString() const = 0; /// Returns the location of the problem (path, line, column) virtual KDevelop::DocumentRange finalLocation() const = 0; /// Sets the location of the problem (path, line, column) virtual void setFinalLocation(const KDevelop::DocumentRange& location) = 0; /// Returns the final location mode of the problem virtual FinalLocationMode finalLocationMode() const = 0; /// Sets the final location mode of the problem virtual void setFinalLocationMode(FinalLocationMode mode) = 0; /// Returns the short description of the problem. virtual QString description() const = 0; /// Sets the short description of the problem virtual void setDescription(const QString& description) = 0; /// Returns the detailed explanation of the problem. virtual QString explanation() const = 0; /// Sets the detailed explanation of the problem virtual void setExplanation(const QString& explanation) = 0; /// Returns the severity of the problem virtual Severity severity() const = 0; /// Sets the severity of the problem virtual void setSeverity(Severity severity) = 0; /// Returns a string containing the severity of the problem virtual QString severityString() const = 0; /// Returns the diagnostics of the problem. virtual QVector diagnostics() const = 0; /// Sets the diagnostics of the problem virtual void setDiagnostics(const QVector &diagnostics) = 0; /// Adds a diagnostic line to the problem virtual void addDiagnostic(const Ptr &diagnostic) = 0; /// Clears all diagnostics virtual void clearDiagnostics() = 0; /// Returns a solution assistant for the problem, if applicable that is. virtual QExplicitlySharedDataPointer solutionAssistant() const = 0; }; Q_DECLARE_OPERATORS_FOR_FLAGS(IProblem::Severities) } Q_DECLARE_METATYPE(KDevelop::IProblem::Ptr) #endif diff --git a/kdevplatform/interfaces/isessionlock.h b/kdevplatform/interfaces/isessionlock.h index 51e122c3d2..8887477817 100644 --- a/kdevplatform/interfaces/isessionlock.h +++ b/kdevplatform/interfaces/isessionlock.h @@ -1,52 +1,52 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef I_SESSIONLOCK_H #define I_SESSIONLOCK_H #include "interfacesexport.h" #include namespace KDevelop { /** * This class ensures that a given session is properly locked and no other * KDevplatform process may use this session. */ class KDEVPLATFORMINTERFACES_EXPORT ISessionLock { public: /** * Use this shared pointer to pass around the session lock. */ - typedef QSharedPointer Ptr; + using Ptr = QSharedPointer; virtual ~ISessionLock(); /** * @return the unique session ID. */ virtual QString id() = 0; }; } #endif // I_SESSIONLOCK_H diff --git a/kdevplatform/interfaces/isourceformatter.h b/kdevplatform/interfaces/isourceformatter.h index 563c32b03d..6f296789c0 100644 --- a/kdevplatform/interfaces/isourceformatter.h +++ b/kdevplatform/interfaces/isourceformatter.h @@ -1,232 +1,232 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur 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 KDEVPLATFORM_ISOURCEFORMATTER_H #define KDEVPLATFORM_ISOURCEFORMATTER_H #include #include "interfacesexport.h" class QUrl; class QMimeType; class QStringList; namespace KDevelop { class KDEVPLATFORMINTERFACES_EXPORT SourceFormatterStyle { public: struct MimeHighlightPair { QString mimeType; QString highlightMode; }; - typedef QVector MimeList; + using MimeList = QVector; SourceFormatterStyle(); explicit SourceFormatterStyle( const QString& name ); void setContent( const QString& content ); void setCaption( const QString& caption ); QString content() const; QString caption() const; QString name() const; QString description() const; void setDescription( const QString& desc ); bool usePreview() const; void setUsePreview(bool use); void setMimeTypes( const MimeList& types ); void setMimeTypes( const QStringList& types ); /// Provides the possibility to the item to return a better-suited /// code sample. If empty, the default is used. QString overrideSample() const; void setOverrideSample( const QString& sample ); MimeList mimeTypes() const; /// mime types as a QVariantList, type and mode separated by | in strings QVariant mimeTypesVariant() const; bool supportsLanguage(const QString& language) const; /// get the language / highlight mode for a given @p mime QString modeForMimetype(const QMimeType& mime) const; /// Copy content, mime types and code sample from @p other. void copyDataFrom(SourceFormatterStyle *other); private: bool m_usePreview = false; QString m_name; QString m_caption; QString m_content; QString m_description; QString m_overrideSample; MimeList m_mimeTypes; }; /** * @brief An object describing a style associated with a plugin * which can deal with this style. */ struct SourceFormatterStyleItem { QString engine; SourceFormatterStyle style; }; -typedef QVector SourceFormatterItemList; +using SourceFormatterItemList = QVector; /** * @short A widget to edit a style * A plugin should inherit this widget to create a widget to * edit a style. * @author Cédric Pasteur */ class KDEVPLATFORMINTERFACES_EXPORT SettingsWidget : public QWidget { Q_OBJECT public: explicit SettingsWidget(QWidget *parent = nullptr); ~SettingsWidget() override; /** This function is called after the creation of the dialog. * it should initialise the widgets with the values corresponding to * the predefined style \arg name if it's not empty, or * to the string \arg content. */ virtual void load(const SourceFormatterStyle&) = 0; /** \return A string representing the state of the config. */ virtual QString save() = 0; Q_SIGNALS: /** Emits this signal when a setting was changed and the preview * needs to be updated. \arg text is the text that will be shown in the * editor. One might want to show different text * according to the different pages shown in the widget. * Text should already be formatted. */ void previewTextChanged(const QString &text); }; /** * @short An interface for a source beautifier * An example of a plugin using an external executable to do * the formatting can be found in kdevelop/plugins/formatters/indent_plugin.cpp. * @author Cédric Pasteur */ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatter { public: virtual ~ISourceFormatter(); /** \return The name of the plugin. This should contain only * ASCII chars and no spaces. This will be used internally to identify * the plugin. */ virtual QString name() const = 0; /** \return A caption describing the plugin. */ virtual QString caption() const = 0; /** \return A more complete description of the plugin. * The string should be written in Rich text. It can eg contain * a link to the project homepage. */ virtual QString description() const = 0; /** Formats using the current style. * @param text The text to format * @param url The URL to which the text belongs (its contents must not be changed). * @param leftContext The context at the left side of the text. If it is in another line, it must end with a newline. * @param rightContext The context at the right side of the text. If it is in the next line, it must start with a newline. * * If the source-formatter cannot work correctly with the context, it will just return the input text. */ virtual QString formatSource(const QString &text, const QUrl& url, const QMimeType &mime, const QString& leftContext = QString(), const QString& rightContext = QString()) const = 0; /** * Format with the given style, this is mostly for the kcm to format the preview text * Its a bit of a hassle that this needs to be public API, but I can't find a better way * to do this. */ virtual QString formatSourceWithStyle( SourceFormatterStyle, const QString& text, const QUrl& url, const QMimeType &mime, const QString& leftContext = QString(), const QString& rightContext = QString() ) const = 0; /** \return A map of predefined styles (a key and a caption for each type) */ virtual QVector predefinedStyles() const = 0; /** \return The widget to edit a style. */ virtual SettingsWidget* editStyleWidget(const QMimeType &mime) const = 0; /** \return The text used in the config dialog to preview the current style. */ virtual QString previewText(const SourceFormatterStyle& style, const QMimeType &mime) const = 0; struct Indentation { Indentation() { } // If this indentation is really valid bool isValid() const { return indentationTabWidth != 0 || indentWidth != 0; } // The length of one tab used for indentation. // Zero if unknown, -1 if tabs should not be used for indentation int indentationTabWidth = 0; // The number of columns that equal one indentation level. // If this is zero, the default should be used. int indentWidth = 0; }; /** \return The indentation of the style applicable for the given url. */ virtual Indentation indentation(const QUrl& url) const = 0; /** \return A string representing the map. Values are written in the form * key=value and separated with ','. */ static QString optionMapToString(const QMap &map); /** \return A map corresponding to the string, that was created with * \ref optionMapToString. */ static QMap stringToOptionMap(const QString &option); /** \return A message to display when an executable needed by a * plugin is missing. This should be returned as description * if a needed executable is not found. */ static QString missingExecutableMessage(const QString &name); }; } Q_DECLARE_INTERFACE(KDevelop::ISourceFormatter, "org.kdevelop.ISourceFormatter") Q_DECLARE_TYPEINFO(KDevelop::SourceFormatterStyle::MimeHighlightPair, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::SourceFormatterStyle, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::SourceFormatterStyleItem, Q_MOVABLE_TYPE); #endif // KDEVPLATFORM_ISOURCEFORMATTER_H diff --git a/kdevplatform/language/assistant/staticassistantsmanager.h b/kdevplatform/language/assistant/staticassistantsmanager.h index 03035b3dd3..b8dadc2673 100644 --- a/kdevplatform/language/assistant/staticassistantsmanager.h +++ b/kdevplatform/language/assistant/staticassistantsmanager.h @@ -1,72 +1,72 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk 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 KDEVPLATFORM_STATICASSISTANTSMANAGER_H #define KDEVPLATFORM_STATICASSISTANTSMANAGER_H #include #include #include "staticassistant.h" #include #include #include #include #include -typedef QPointer SafeDocumentPointer; +using SafeDocumentPointer = QPointer; namespace KDevelop { class IDocument; class DUContext; class TopDUContext; /** * @brief Class managing instances of StaticAssistant * * Invokes the appropriate methods on registered StaticAssistant instances, such as StaticAssistant::textChanged * * @sa StaticAssistant::textChanged */ class KDEVPLATFORMLANGUAGE_EXPORT StaticAssistantsManager : public QObject { Q_OBJECT public: explicit StaticAssistantsManager(QObject* parent = nullptr); ~StaticAssistantsManager() override; void registerAssistant(const StaticAssistant::Ptr& assistant); void unregisterAssistant(const StaticAssistant::Ptr& assistant); QVector registeredAssistants() const; void notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context); QVector problemsForContext(const ReferencedTopDUContext& top); Q_SIGNALS: void problemsChanged(const IndexedString& url); private: const QScopedPointer d; }; } #endif // KDEVPLATFORM_STATICASSISTANTSMANAGER_H diff --git a/kdevplatform/language/backgroundparser/documentchangetracker.h b/kdevplatform/language/backgroundparser/documentchangetracker.h index 03a5115054..7e8d4107eb 100644 --- a/kdevplatform/language/backgroundparser/documentchangetracker.h +++ b/kdevplatform/language/backgroundparser/documentchangetracker.h @@ -1,258 +1,258 @@ /* * This file is part of KDevelop * * Copyright 2010 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 KDEVPLATFORM_DOCUMENTCHANGETRACKER_H #define KDEVPLATFORM_DOCUMENTCHANGETRACKER_H #include #include #include #include #include #include #include namespace KTextEditor { class Document; class MovingRange; class MovingInterface; } namespace KDevelop { class DocumentChangeTracker; /** * These objects belongs to the foreground, and thus can only be accessed from background threads if the foreground lock is held. * */ class RevisionLockerAndClearerPrivate; /** * Helper class that locks a revision, and clears it on its destruction within the foreground thread. * Just delete it using deleteLater(). * */ class KDEVPLATFORMLANGUAGE_EXPORT RevisionLockerAndClearer : public QSharedData { public: - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; ~RevisionLockerAndClearer(); /** * Returns the revision number * */ qint64 revision() const; /** * Whether the revision is still being held. It may have been lost due to document-reloads, * in which case the revision must not be used. * */ bool valid() const; /** * Transform a range from this document revision to the given @p to. * */ RangeInRevision transformToRevision(const RangeInRevision& range, const Ptr& to) const; /** * Transform a cursor from this document revision to the given @p to. * If a zero target revision is given, the transformation is done to the current document revision. * */ CursorInRevision transformToRevision(const CursorInRevision& cursor, const Ptr& to, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transforms the given range from this revision into the current revision. */ KTextEditor::Range transformToCurrentRevision(const RangeInRevision& range) const; /** * Transforms the given cursor from this revision into the current revision. */ KTextEditor::Cursor transformToCurrentRevision(const CursorInRevision& cursor, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transform ranges from the given document revision @p from to the this one. * If a zero @p from revision is given, the transformation is done from the current document revision. * */ RangeInRevision transformFromRevision(const RangeInRevision& range, const Ptr& from = Ptr()) const; /** * Transform ranges from the given document revision @p from to the this one. * If a zero @p from revision is given, the transformation is done from the current document revision. * */ CursorInRevision transformFromRevision(const CursorInRevision& cursor, const Ptr& from = Ptr(), KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transforms the given range from the current revision into this revision. */ RangeInRevision transformFromCurrentRevision(const KTextEditor::Range& range) const; /** * Transforms the given cursor from the current revision into this revision. */ CursorInRevision transformFromCurrentRevision(const KTextEditor::Cursor& cursor, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; private: friend class DocumentChangeTracker; RevisionLockerAndClearerPrivate* m_p; }; -typedef RevisionLockerAndClearer::Ptr RevisionReference; +using RevisionReference = RevisionLockerAndClearer::Ptr; class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeTracker : public QObject { Q_OBJECT public: explicit DocumentChangeTracker(KTextEditor::Document* document); ~DocumentChangeTracker() override; /** * Completions of the users current edits that are supposed to complete * not-yet-finished statements, like for example for-loops for parsing. * */ virtual QList> completions() const; /** * Resets the tracking to the current revision. * */ virtual void reset(); /** * Returns the document revision at which reset() was called last. * * The revision is being locked by the tracker in MovingInterface, * it will be unlocked as soon as reset() is called, so if you want to use * the revision afterwards, you have to lock it before calling reset. * * zero is returned if the revisions were invalidated after the last call. * */ RevisionReference revisionAtLastReset() const; /** * Returns the current revision (which is not locked by the tracker) * */ RevisionReference currentRevision(); /** * Whether the changes that happened since the last reset are significant enough to require an update * */ virtual bool needUpdate() const; /** * Returns the tracked document **/ KTextEditor::Document* document() const; KTextEditor::MovingInterface* documentMovingInterface() const; /** * Returns the revision object which locks the revision representing the on-disk state. * Returns a zero object if the file is not on disk. * */ RevisionReference diskRevision() const; /** * Returns whether the given revision is being current held, so that it can be used * for transformations in MovingInterface * */ bool holdingRevision(qint64 revision) const; /** * Use this function to acquire a revision. As long as the returned object is stored somewhere, * the revision can be used for transformations in MovingInterface, and especially for * DocumentChangeTracker::transformBetweenRevisions. * * Returns a zero revision object if the revision could not be acquired (it wasn't held). * */ RevisionReference acquireRevision(qint64 revision); /** * Safely maps the given range between the two given revisions. * The mapping is only done if both the from- and to- revision are held, * else the original range is returned. * * @warning: Make sure that you actually hold the referenced revisions, else no transformation will be done. * @note It is much less error-prone to use RevisionReference->transformToRevision() and RevisionReference->transformFromRevision() directly. * */ RangeInRevision transformBetweenRevisions(RangeInRevision range, qint64 fromRevision, qint64 toRevision) const; CursorInRevision transformBetweenRevisions(CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; KTextEditor::Range transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const; KTextEditor::Cursor transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /// Transform the range from the current revision into the given one RangeInRevision transformToRevision(KTextEditor::Range range, qint64 toRevision) const; /// Transform the cursor from the current revision into the given one CursorInRevision transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; protected: RevisionReference m_revisionAtLastReset; bool m_needUpdate; QString m_currentCleanedInsertion; KTextEditor::Cursor m_lastInsertionPosition; KTextEditor::MovingRange* m_changedRange; KTextEditor::Document* m_document; KTextEditor::MovingInterface* m_moving; KDevelop::IndexedString m_url; void updateChangedRange(int delay); int recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text, bool removal); public Q_SLOTS: void textInserted(KTextEditor::Document* document, const KTextEditor::Cursor& position, const QString& inserted); void textRemoved(KTextEditor::Document* document, const KTextEditor::Range& range, const QString& oldText); void lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position); void lineUnwrapped(KTextEditor::Document* document, int line); void documentDestroyed(QObject*); void aboutToInvalidateMovingInterfaceContent (KTextEditor::Document* document); void documentSavedOrUploaded(KTextEditor::Document*, bool); private: bool checkMergeTokens(const KTextEditor::Range& range); friend class RevisionLockerAndClearerPrivate; void lockRevision(qint64 revision); void unlockRevision(qint64 revision); QMap m_revisionLocks; }; } #endif diff --git a/kdevplatform/language/backgroundparser/parsejob.cpp b/kdevplatform/language/backgroundparser/parsejob.cpp index a8d95a9066..fdd56c04b7 100644 --- a/kdevplatform/language/backgroundparser/parsejob.cpp +++ b/kdevplatform/language/backgroundparser/parsejob.cpp @@ -1,537 +1,537 @@ /* * 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 "backgroundparser.h" #include #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include "editor/documentrange.h" #include #include #include #include #include #include #include #include #include using namespace KTextEditor; static QMutex minimumFeaturesMutex; static QHash> staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) : url(url_) , languageSupport(languageSupport_) , abortRequested(0) , hasReadContents(false) , aborted(false) , features(TopDUContext::VisibleDeclarationsAndContexts) , parsePriority(0) , sequentialProcessingFlags(ParseJob::IgnoresSequentialProcessing) { } ~ParseJobPrivate() { } ReferencedTopDUContext duContext; IndexedString url; ILanguageSupport* languageSupport; ParseJob::Contents contents; QAtomicInt abortRequested; bool hasReadContents : 1; bool aborted : 1; TopDUContext::Features features; QVector> notify; QPointer tracker; RevisionReference revision; RevisionReference previousRevision; int parsePriority; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; }; ParseJob::ParseJob(const IndexedString& url, KDevelop::ILanguageSupport* languageSupport) : ThreadWeaver::Sequence() , d(new ParseJobPrivate(url, languageSupport)) { } ParseJob::~ParseJob() { - typedef QPointer QObjectPointer; + using QObjectPointer = QPointer; foreach (const QObjectPointer& p, d->notify) { if (p) { QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->url), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); } } } ILanguageSupport* ParseJob::languageSupport() const { return d->languageSupport; } void ParseJob::setParsePriority(int priority) { d->parsePriority = priority; } int ParseJob::parsePriority() const { return d->parsePriority; } bool ParseJob::requiresSequentialProcessing() const { return d->sequentialProcessingFlags & RequiresSequentialProcessing; } bool ParseJob::respectsSequentialProcessing() const { return d->sequentialProcessingFlags & RespectsSequentialProcessing; } void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags) { d->sequentialProcessingFlags = flags; } IndexedString ParseJob::document() const { return d->url; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); return !::staticMinimumFeatures.isEmpty(); } TopDUContext::Features ParseJob::staticMinimumFeatures(const IndexedString& url) { QMutexLocker lock(&minimumFeaturesMutex); auto 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->url)); } void ParseJob::setDuChain(const ReferencedTopDUContext& duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } bool ParseJob::abortRequested() const { return d->abortRequested.load(); } void ParseJob::requestAbort() { d->abortRequested = 1; } void ParseJob::abortJob() { d->aborted = true; setStatus(Status_Aborted); } void ParseJob::setNotifyWhenReady(const QVector>& notify) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(const 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(); 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(); qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } bool hadTracker = false; if (d->tracker) { ForegroundLock lock; 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->document()->text().toUtf8(); d->contents.modification = KDevelop::ModificationRevision(lastModified, t->revisionAtLastReset()->revision()); d->revision = t->acquireRevision(d->contents.modification.revision); hadTracker = true; } } if (!hadTracker) { // We have to load the file from disk static const int maximumFileSize = 5 * 1024 * 1024; // 5 MB if (fileInfo.size() > maximumFileSize) { QStringList paths = QStandardPaths::standardLocations(QStandardPaths::StandardLocation::GenericDataLocation); bool internalFile = false; QString internalFilePath = fileInfo.canonicalPath(); foreach (const QString path, paths) { QDir dataPath = QDir(path); if (internalFilePath.startsWith(dataPath.canonicalPath() + QStringLiteral("/kdev"))) { qCDebug(LANGUAGE) << "Found internal file " << fileInfo.absoluteFilePath() << " in " << path << ". Ignoring file size limit!"; internalFile = true; break; } } if (!internalFile) { KFormat f; KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile)); p->setExplanation(i18nc("%1: file size, %2: limit file size", "The file is %1 and exceeds the limit of %2.", f.formatByteSize(fileInfo.size()), f.formatByteSize(maximumFileSize))); p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << p->description() << p->explanation(); return p; } } QFile file(localFile); if (!file.open(QIODevice::ReadOnly)) { KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::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(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << "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 // This is consistent with KTextEditor::Document::text() as used for already-open files. normalizeLineEndings(d->contents.contents); d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } 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) { } void visit(DUContext* context) override { 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); } } void visit(Declaration* declaration) override { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow // or translate the start of the top context away from (0, 0) if (r.start.line != 0 || r.start.column != 0) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); } if (r.end.line != std::numeric_limits::max() || r.end.column != std::numeric_limits::max()) { 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) { qCDebug(LANGUAGE) << "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) { qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision; return; } } if (sourceRevision > targetRevision) { qCDebug(LANGUAGE) << "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) { qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision"; return; } if (sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { qCDebug(LANGUAGE) << "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)) { qCDebug(LANGUAGE) << "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 problems = context->problems(); for (QList::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); } } bool ParseJob::isUpdateRequired(const IndexedString& languageString) { if (abortRequested()) { return false; } if (minimumFeatures() & TopDUContext::ForceUpdate) { return true; } DUChainReadLocker lock; if (abortRequested()) { return false; } foreach (const ParsingEnvironmentFilePointer& file, DUChain::self()->allEnvironmentFiles(document())) { if (file->language() != languageString) { continue; } if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) { qCDebug(LANGUAGE) << "Already up to date" << document().str(); setDuChain(file->topContext()); lock.unlock(); highlightDUChain(); return false; } break; } return !abortRequested(); } const ParsingEnvironment* ParseJob::environment() const { return nullptr; } void ParseJob::highlightDUChain() { ENSURE_CHAIN_NOT_LOCKED if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) { // language doesn't support highlighting return; } if (!d->hasReadContents && !d->tracker) { d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); } if (d->tracker) { d->languageSupport->codeHighlighting()->highlightDUChain(duChain()); } } ControlFlowGraph* ParseJob::controlFlowGraph() { return nullptr; } DataAccessRepository* ParseJob::dataAccessInformation() { return nullptr; } bool ParseJob::hasTracker() const { return d->tracker; } } diff --git a/kdevplatform/language/classmodel/classmodelnode.h b/kdevplatform/language/classmodel/classmodelnode.h index 4daa372554..a2a67879a9 100644 --- a/kdevplatform/language/classmodel/classmodelnode.h +++ b/kdevplatform/language/classmodel/classmodelnode.h @@ -1,339 +1,339 @@ /* * KDevelop Class Browser * * Copyright 2007-2009 Hamish Rodda * Copyright 2009 Lior Mualem * * 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 KDEVPLATFORM_CLASSMODELNODE_H #define KDEVPLATFORM_CLASSMODELNODE_H #include "classmodel.h" #include "../duchain/identifier.h" #include "../duchain/duchainpointer.h" #include "classmodelnodescontroller.h" #include class NodesModelInterface; namespace KDevelop { class ClassDeclaration; class ClassFunctionDeclaration; class ClassMemberDeclaration; class Declaration; } namespace ClassModelNodes { /// Base node class - provides basic functionality. class Node { public: Node(const QString& a_displayName, NodesModelInterface* a_model); virtual ~Node(); public: // Operations /// Clear all the children from the node. void clear(); /// Called by the model to collapse the node and remove sub-items if needed. virtual void collapse() {}; /// Called by the model to expand the node and populate it with sub-nodes if needed. virtual void expand() {}; /// Append a new child node to the list. void addNode(Node* a_child); /// Remove child node from the list and delete it. void removeNode(Node* a_child); /// Remove this node and delete it. void removeSelf() { m_parentNode->removeNode(this); } /// Called once the node has been populated to sort the entire tree / branch. void recursiveSort(); public: // Info retrieval /// Return the parent associated with this node. Node* parent() const { return m_parentNode; } /// Get my index in the parent node int row(); /// Return the display name for the node. QString displayName() const { return m_displayName; } /// Returns a list of child nodes const QList& children() const { return m_children; } /// Return an icon representation for the node. /// @note It calls the internal getIcon and caches the result. QIcon cachedIcon(); public: // overridables /// Return a score when sorting the nodes. virtual int score() const = 0; /// Return true if the node contains sub-nodes. virtual bool hasChildren() const { return !m_children.empty(); } /// We use this string when sorting items. virtual QString sortableString() const { return m_displayName; } protected: /// fill a_resultIcon with a display icon for the node. /// @param a_resultIcon returned icon. /// @return true if result was returned. virtual bool getIcon(QIcon& a_resultIcon) = 0; private: Node* m_parentNode; /// Called once the node has been populated to sort the entire tree / branch. void recursiveSortInternal(); protected: - typedef QList NodesList; + using NodesList = QList; NodesList m_children; QString m_displayName; QIcon m_cachedIcon; NodesModelInterface* m_model; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes that generate and populate their child nodes dynamically class DynamicNode : public Node { public: DynamicNode(const QString& a_displayName, NodesModelInterface* a_model); /// Return true if the node was populated already. bool isPopulated() const { return m_populated; } /// Populate the node and mark the flag - called from expand or can be used internally. void performPopulateNode(bool a_forceRepopulate = false); public: // Node overrides. void collapse() override; void expand() override; bool hasChildren() const override; protected: // overridables /// Called by the framework when the node is about to be expanded /// it should be populated with sub-nodes if applicable. virtual void populateNode() {} /// Called after the nodes have been removed. /// It's for derived classes to clean cached data. virtual void nodeCleared() {} private: bool m_populated; /// Clear all the child nodes and mark flag. void performNodeCleanup(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes associated with a @ref KDevelop::QualifiedIdentifier class IdentifierNode : public DynamicNode { public: IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName = QString()); public: /// Returns the qualified identifier for this node by going through the tree const KDevelop::IndexedQualifiedIdentifier& identifier() const { return m_identifier; } public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; public: // Overridables /// Return the associated declaration /// @note DU CHAIN MUST BE LOCKED FOR READ virtual KDevelop::Declaration* declaration(); private: KDevelop::IndexedQualifiedIdentifier m_identifier; KDevelop::IndexedDeclaration m_indexedDeclaration; KDevelop::DeclarationPointer m_cachedDeclaration; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// A node that represents an enum value. class EnumNode : public IdentifierNode { public: EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int score() const override { return 102; } bool getIcon(QIcon& a_resultIcon) override; void populateNode() override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class. class ClassNode : public IdentifierNode , public ClassModelNodeDocumentChangedInterface { public: ClassNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); ~ClassNode() override; /// Lookup a contained class and return the related node. /// @return the node pointer or 0 if non was found. ClassNode* findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id); public: // Node overrides int score() const override { return 300; } void populateNode() override; void nodeCleared() override; bool hasChildren() const override { return true; } protected: // ClassModelNodeDocumentChangedInterface overrides void documentChanged(const KDevelop::IndexedString& a_file) override; private: - typedef QMap SubIdentifiersMap; + using SubIdentifiersMap = QMap; /// Set of known sub-identifiers. It's used for updates check. SubIdentifiersMap m_subIdentifiers; /// We use this variable to know if we've registered for change notification or not. KDevelop::IndexedString m_cachedUrl; /// Updates the node to reflect changes in the declaration. /// @note DU CHAIN MUST BE LOCKED FOR READ /// @return true if something was updated. bool updateClassDeclarations(); /// Add "Base classes" and "Derived classes" folders, if needed /// @return true if one of the folders was added. bool addBaseAndDerived(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a display for a single class function. class FunctionNode : public IdentifierNode { public: FunctionNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int score() const override { return 400; } QString sortableString() const override { return m_sortableString; } private: QString m_sortableString; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class variable. class ClassMemberNode : public IdentifierNode { public: ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int score() const override { return 500; } bool getIcon(QIcon& a_resultIcon) override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a static list of nodes. class FolderNode : public Node { public: FolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int score() const override { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a dynamic list of nodes. class DynamicFolderNode : public DynamicNode { public: DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int score() const override { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays the base classes for the class it sits in. class BaseClassesFolderNode : public DynamicFolderNode { public: explicit BaseClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides void populateNode() override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays list of derived classes from the parent class. class DerivedClassesFolderNode : public DynamicFolderNode { public: explicit DerivedClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides void populateNode() override; }; } // namespace classModelNodes #endif diff --git a/kdevplatform/language/classmodel/classmodelnodescontroller.h b/kdevplatform/language/classmodel/classmodelnodescontroller.h index 32b3ef9ea1..c59957f311 100644 --- a/kdevplatform/language/classmodel/classmodelnodescontroller.h +++ b/kdevplatform/language/classmodel/classmodelnodescontroller.h @@ -1,75 +1,75 @@ /* * KDevelop Class Browser * * Copyright 2009 Lior Mualem * * 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 KDEVPLATFORM_CLASSMODELNODESCONTROLLER_H #define KDEVPLATFORM_CLASSMODELNODESCONTROLLER_H #include #include "../../serialization/indexedstring.h" #include "../duchain/ducontext.h" class QTimer; class ClassModelNodeDocumentChangedInterface { public: virtual ~ClassModelNodeDocumentChangedInterface(); /// Called when the registered document is changed. virtual void documentChanged(const KDevelop::IndexedString& a_file) = 0; }; /// This class provides notifications for updates between the different nodes /// and the various kdevelop sub-systems (such as notification when a DUChain gets /// updated). class ClassModelNodesController : public QObject { Q_OBJECT public: ClassModelNodesController(); ~ClassModelNodesController() override; static ClassModelNodesController& self(); /// Register the given class node to receive notifications about its top context changes. void registerForChanges(const KDevelop::IndexedString& a_file, ClassModelNodeDocumentChangedInterface* a_node); /// Unregister the given class node from further notifications. void unregisterForChanges(const KDevelop::IndexedString& a_file, ClassModelNodeDocumentChangedInterface* a_node); private Q_SLOTS: // Files update. void updateChangedFiles(); private: // File updates related. /// List of updated files we check this list when update timer expires. QSet m_updatedFiles; /// Timer for batch updates. QTimer* m_updateTimer; - typedef QMultiMap FilesMap; + using FilesMap = QMultiMap; /// Maps between monitored files and their class nodes. FilesMap m_filesMap; }; #endif diff --git a/kdevplatform/language/classmodel/documentclassesfolder.h b/kdevplatform/language/classmodel/documentclassesfolder.h index 749dcdde31..be95b87ce3 100644 --- a/kdevplatform/language/classmodel/documentclassesfolder.h +++ b/kdevplatform/language/classmodel/documentclassesfolder.h @@ -1,155 +1,155 @@ /* * KDevelop Class Browser * * Copyright 2009 Lior Mualem * * 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 KDEVPLATFORM_DOCUMENTCLASSESFOLDER_H #define KDEVPLATFORM_DOCUMENTCLASSESFOLDER_H #include "classmodelnode.h" #include #include #include namespace ClassModelNodes { class StaticNamespaceFolderNode; /// This folder displays all the classes that relate to a list of documents. class DocumentClassesFolder : public QObject , public DynamicFolderNode { Q_OBJECT public: DocumentClassesFolder(const QString& a_displayName, NodesModelInterface* a_model); public: // Operations /// Find a class node in the lists by its id. ClassNode* findClassNode(const KDevelop::IndexedQualifiedIdentifier& a_id); protected: // Documents list handling. /// Parse a single document for classes and add them to the list. void parseDocument(const KDevelop::IndexedString& a_file); /// Re-parse the given document - remove old declarations and add new declarations. bool updateDocument(const KDevelop::IndexedString& a_file); /// Close and remove all the nodes related to the specified document. void closeDocument(const KDevelop::IndexedString& a_file); /// Returns a list of documents we have monitored. QSet allOpenDocuments() const; protected: // Overridables /// Override this to filter the found classes. virtual bool isClassFiltered(const KDevelop::QualifiedIdentifier&) { return false; } public: // Node overrides void nodeCleared() override; void populateNode() override; bool hasChildren() const override { return true; } private Q_SLOTS: // Files update. void updateChangedFiles(); private: // File updates related. /// List of updated files we check this list when update timer expires. QSet m_updatedFiles; /// Timer for batch updates. QTimer* m_updateTimer; private: // Opened class identifiers container definition. // An opened class item. struct OpenedFileClassItem { OpenedFileClassItem(); OpenedFileClassItem(const KDevelop::IndexedString& a_file, const KDevelop::IndexedQualifiedIdentifier& a_classIdentifier, ClassNode* a_nodeItem); /// The file this class declaration comes from. KDevelop::IndexedString file; /// The identifier for this class. KDevelop::IndexedQualifiedIdentifier classIdentifier; /// An existing node item. It maybe 0 - meaning the class node is currently hidden. ClassNode* nodeItem; }; // Index definitions. struct FileIndex {}; struct ClassIdentifierIndex {}; // Member types definitions. - typedef boost::multi_index::member< + using FileMember = boost::multi_index::member< OpenedFileClassItem, KDevelop::IndexedString, - & OpenedFileClassItem::file> FileMember; - typedef boost::multi_index::member< + & OpenedFileClassItem::file>; + using ClassIdentifierMember = boost::multi_index::member< OpenedFileClassItem, KDevelop::IndexedQualifiedIdentifier, - & OpenedFileClassItem::classIdentifier> ClassIdentifierMember; + & OpenedFileClassItem::classIdentifier>; // Container definition. - typedef boost::multi_index::multi_index_container< + using OpenFilesContainer = boost::multi_index::multi_index_container< OpenedFileClassItem, boost::multi_index::indexed_by< boost::multi_index::ordered_non_unique< boost::multi_index::tag, FileMember >, boost::multi_index::ordered_unique< boost::multi_index::tag, ClassIdentifierMember > > - > OpenFilesContainer; + >; // Iterators definition. - typedef OpenFilesContainer::index_iterator::type FileIterator; - typedef OpenFilesContainer::index_iterator::type ClassIdentifierIterator; + using FileIterator = OpenFilesContainer::index_iterator::type; + using ClassIdentifierIterator = OpenFilesContainer::index_iterator::type; /// Maps all displayed classes and their referenced files. OpenFilesContainer m_openFilesClasses; /// Holds a set of open files. QSet m_openFiles; private: - typedef QMap NamespacesMap; + using NamespacesMap = QMap; /// Holds a map between an identifier and a namespace folder we hold. NamespacesMap m_namespaces; /// Recursively create a namespace folder for the specified identifier if it doesn't /// exist, cache it and return it (or just return it from the cache). StaticNamespaceFolderNode* namespaceFolder(const KDevelop::QualifiedIdentifier& a_identifier); /// Removes the given namespace identifier recursively if it's empty. void removeEmptyNamespace(const KDevelop::QualifiedIdentifier& a_identifier); /// Remove a single class node from the lists. void removeClassNode(ClassNode* a_node); }; } // namespace ClassModelNodes #endif // KDEVPLATFORM_DOCUMENTCLASSESFOLDER_H diff --git a/kdevplatform/language/codecompletion/codecompletioncontext.cpp b/kdevplatform/language/codecompletion/codecompletioncontext.cpp index 116797d8ae..2507b581f4 100644 --- a/kdevplatform/language/codecompletion/codecompletioncontext.cpp +++ b/kdevplatform/language/codecompletion/codecompletioncontext.cpp @@ -1,105 +1,105 @@ /* Copyright 2007 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 "codecompletioncontext.h" #include #include #include using namespace KDevelop; -typedef PushValue IntPusher; +using IntPusher = PushValue; ///Extracts the last line from the given string QString CodeCompletionContext::extractLastLine(const QString& str) { int prevLineEnd = str.lastIndexOf(QLatin1Char('\n')); if (prevLineEnd != -1) return str.mid(prevLineEnd + 1); else return str; } int completionRecursionDepth = 0; CodeCompletionContext::CodeCompletionContext(const DUContextPointer& context, const QString& text, const KDevelop::CursorInRevision& position, int depth) : m_text(text) , m_depth(depth) , m_valid(true) , m_position(position) , m_duContext(context) , m_parentContext(nullptr) { IntPusher(completionRecursionDepth, completionRecursionDepth + 1); if (depth > 10) { qCWarning(LANGUAGE) << "too much recursion"; m_valid = false; return; } if (completionRecursionDepth > 10) { qCWarning(LANGUAGE) << "too much recursion"; m_valid = false; return; } } CodeCompletionContext::~CodeCompletionContext() { } int CodeCompletionContext::depth() const { return m_depth; } bool CodeCompletionContext::isValid() const { return m_valid; } void KDevelop::CodeCompletionContext::setParentContext( QExplicitlySharedDataPointer newParent) { m_parentContext = newParent; int newDepth = m_depth + 1; while (newParent) { newParent->m_depth = newDepth; ++newDepth; newParent = newParent->m_parentContext; } } CodeCompletionContext* CodeCompletionContext::parentContext() { return m_parentContext.data(); } QList> KDevelop::CodeCompletionContext::ungroupedElements() { return QList>(); } KDevelop::DUContext* KDevelop::CodeCompletionContext::duContext() const { return m_duContext.data(); } diff --git a/kdevplatform/language/codecompletion/codecompletioncontext.h b/kdevplatform/language/codecompletion/codecompletioncontext.h index c53b32c8b1..3b868679ba 100644 --- a/kdevplatform/language/codecompletion/codecompletioncontext.h +++ b/kdevplatform/language/codecompletion/codecompletioncontext.h @@ -1,114 +1,114 @@ /* Copyright 2007 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 KDEVPLATFORM_CODECOMPLETIONCONTEXT_H #define KDEVPLATFORM_CODECOMPLETIONCONTEXT_H #include "../duchain/duchainpointer.h" #include #include "../editor/cursorinrevision.h" #include "codecompletionitem.h" namespace KTextEditor { class View; class Cursor; } namespace KDevelop { class CursorInRevision; class CompletionTreeItem; class CompletionTreeElement; -typedef QExplicitlySharedDataPointer CompletionTreeItemPointer; -typedef QExplicitlySharedDataPointer CompletionTreeElementPointer; +using CompletionTreeItemPointer = QExplicitlySharedDataPointer; +using CompletionTreeElementPointer = QExplicitlySharedDataPointer; /** * This class is responsible for finding out what kind of completion is needed, what expression should be evaluated for the container-class of the completion, what conversion will be applied to the result of the completion, etc. * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionContext : public QSharedData { public: - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; /** * @param text the text to analyze. It usually is the text in the range starting at the beginning of the context, * and ending at the position where completion should start * * @warning The du-chain must be unlocked when this is called * */ CodeCompletionContext(const KDevelop::DUContextPointer& context, const QString& text, const KDevelop::CursorInRevision& position, int depth = 0); virtual ~CodeCompletionContext(); /** * @return Whether this context is valid for code-completion */ bool isValid() const; /** * @return Depth of the context. The basic completion-context has depth 0, its parent 1, and so on.. */ int depth() const; /** * Computes the full set of completion items, using the information retrieved earlier. * Should only be called on the first context, parent contexts are included in the computations. * * @param abort Checked regularly, and if false, the computation is aborted. * * @warning Please check @p abort and @p isValid when reimplementing this method */ virtual QList completionItems(bool& abort, bool fullCompletion = true) = 0; /** * After completionItems(..) has been called, this may return completion-elements that are already grouped, * for example using custom grouping(@see CompletionCustomGroupNode */ virtual QList ungroupedElements(); /** * In the case of recursive argument-hints, there may be a chain of parent-contexts, each for the higher argument-matching * The parentContext() should always have the access-operation FunctionCallAccess. * When a completion-list is computed, the members of the list can be highlighted that match * the corresponding parentContext()->functions() function-argument, or parentContext()->additionalMatchTypes() */ CodeCompletionContext* parentContext(); ///Sets the new parent context, and also updates the depth void setParentContext(QExplicitlySharedDataPointer newParent); DUContext* duContext() const; protected: static QString extractLastLine(const QString& str); QString m_text; int m_depth; bool m_valid; KDevelop::CursorInRevision m_position; KDevelop::DUContextPointer m_duContext; QExplicitlySharedDataPointer m_parentContext; }; } Q_DECLARE_METATYPE(KDevelop::CodeCompletionContext::Ptr) #endif diff --git a/kdevplatform/language/codecompletion/codecompletionitem.h b/kdevplatform/language/codecompletion/codecompletionitem.h index 8c97460196..3fe522d2ba 100644 --- a/kdevplatform/language/codecompletion/codecompletionitem.h +++ b/kdevplatform/language/codecompletion/codecompletionitem.h @@ -1,157 +1,157 @@ /* * KDevelop Generic Code Completion Support * * 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. */ #ifndef KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H #define KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H #include "../duchain/duchainpointer.h" #include namespace KTextEditor { class CodeCompletionModel; class Range; class Cursor; } class QModelIndex; namespace KDevelop { class CodeCompletionModel; struct CompletionTreeNode; class CompletionTreeItem; class IndexedType; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeElement : public QSharedData { public: CompletionTreeElement(); virtual ~CompletionTreeElement(); CompletionTreeElement* parent() const; /// Reparenting is not supported. This is only allowed if parent() is still zero. void setParent(CompletionTreeElement*); int rowInParent() const; int columnInParent() const; /// Each element is either a node, or an item. CompletionTreeNode* asNode(); CompletionTreeItem* asItem(); template T* asItem() { return dynamic_cast(this); } template const T* asItem() const { return dynamic_cast(this); } const CompletionTreeNode* asNode() const; const CompletionTreeItem* asItem() const; private: CompletionTreeElement* m_parent; int m_rowInParent; }; struct KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeNode : public CompletionTreeElement { CompletionTreeNode(); ~CompletionTreeNode() override; KTextEditor::CodeCompletionModel::ExtraItemDataRoles role; QVariant roleValue; /// Will append the child, and initialize it correctly to create a working tree-structure void appendChild(QExplicitlySharedDataPointer ); void appendChildren(const QList>& children); void appendChildren(const QList>& children); /// @warning Do not manipulate this directly, that's bad for consistency. Use appendChild instead. QList> children; }; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeItem : public CompletionTreeElement { public: /// Execute the completion item. The default implementation does nothing. virtual void execute(KTextEditor::View* view, const KTextEditor::Range& word); /// Should return normal completion data, @see KTextEditor::CodeCompletionModel /// The default implementation returns "unimplemented", so re-implement it! /// The duchain is not locked when this is called virtual QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const; /// Should return the inheritance-depth. The completion-items don't need to return it through the data() function. virtual int inheritanceDepth() const; /// Should return the argument-hint depth. The completion-items don't need to return it through the data() function. virtual int argumentHintDepth() const; /// The default-implementation calls DUChainUtils::completionProperties virtual KTextEditor::CodeCompletionModel::CompletionProperties completionProperties() const; /// If this item represents a Declaration, this should return the declaration. /// The default-implementation returns zero. virtual DeclarationPointer declaration() const; /// Should return the types should be used for matching items against this one when it's an argument hint. /// The matching against all types should be done, and the best one will be used as final match result. virtual QList typeForArgumentMatching() const; /// Should return whether this completion-items data changes with input done by the user during code-completion. /// Returning true is very expensive. virtual bool dataChangedWithInput() const; }; /// A custom-group node, that can be used as-is. Just create it, and call appendChild to add group items. /// The items in the group will be shown in the completion-list with a group-header that contains the given name struct KDEVPLATFORMLANGUAGE_EXPORT CompletionCustomGroupNode : public CompletionTreeNode { /// @param inheritanceDepth See KTextEditor::CodeCompletionModel::GroupRole explicit CompletionCustomGroupNode(const QString& groupName, int inheritanceDepth = 700); int inheritanceDepth; }; -typedef QExplicitlySharedDataPointer CompletionTreeItemPointer; -typedef QExplicitlySharedDataPointer CompletionTreeElementPointer; +using CompletionTreeItemPointer = QExplicitlySharedDataPointer; +using CompletionTreeElementPointer = QExplicitlySharedDataPointer; } Q_DECLARE_METATYPE(KDevelop::CompletionTreeElementPointer) #endif diff --git a/kdevplatform/language/codecompletion/codecompletionitemgrouper.h b/kdevplatform/language/codecompletion/codecompletionitemgrouper.h index 8bff474dd5..7611682fc3 100644 --- a/kdevplatform/language/codecompletion/codecompletionitemgrouper.h +++ b/kdevplatform/language/codecompletion/codecompletionitemgrouper.h @@ -1,121 +1,121 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 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. */ #ifndef KDEVPLATFORM_CODECOMPLETIONITEMGROUPER_H #define KDEVPLATFORM_CODECOMPLETIONITEMGROUPER_H #include "codecompletionmodel.h" #include "codecompletionitem.h" #include #include namespace KDevelop { ///Always the last item of a grouping chain: Only inserts the items struct CodeCompletionItemLastGrouper { CodeCompletionItemLastGrouper(QList>& tree, CompletionTreeNode* parent, const QList& items) { tree.reserve(tree.size() + items.size()); for (auto& item : items) { item->setParent(parent); tree << QExplicitlySharedDataPointer(item.data()); } } }; ///Helper class that helps us grouping the completion-list. A chain of groupers can be built, by using NextGrouper. template struct CodeCompletionItemGrouper { - typedef typename KeyExtractor::KeyType KeyType; + using KeyType = typename KeyExtractor::KeyType; CodeCompletionItemGrouper(QList>& tree, CompletionTreeNode* parent, const QList& items) { - typedef QMap> GroupMap; + using GroupMap = QMap>; GroupMap groups; for (auto& item : items) { KeyType key = KeyExtractor::extract(item); typename GroupMap::iterator it = groups.find(key); if (it == groups.end()) it = groups.insert(key, QList()); (*it).append(item); } tree.reserve(tree.size() + groups.size()); for (typename GroupMap::const_iterator it = groups.constBegin(); it != groups.constEnd(); ++it) { QExplicitlySharedDataPointer node(new CompletionTreeNode()); node->setParent(parent); node->role = ( KTextEditor::CodeCompletionModel::ExtraItemDataRoles )KeyExtractor::Role; node->roleValue = QVariant(it.key()); tree << QExplicitlySharedDataPointer(node.data()); NextGrouper nextGrouper(node->children, node.data(), *it); } } }; ///Extracts the argument-hint depth from completion-items, to be used in ItemGrouper for grouping by argument-hint depth. struct ArgumentHintDepthExtractor { - typedef int KeyType; + using KeyType = int; enum { Role = KTextEditor::CodeCompletionModel::ArgumentHintDepth }; static KeyType extract(const CompletionTreeItemPointer& item) { return item->argumentHintDepth(); } }; struct InheritanceDepthExtractor { - typedef int KeyType; + using KeyType = int; enum { Role = KTextEditor::CodeCompletionModel::InheritanceDepth }; static KeyType extract(const CompletionTreeItemPointer& item) { return item->inheritanceDepth(); } }; struct SimplifiedAttributesExtractor { - typedef int KeyType; + using KeyType = int; enum { Role = KTextEditor::CodeCompletionModel::CompletionRole }; static const int groupingProperties; static KeyType extract(const CompletionTreeItemPointer& item) { DUChainReadLocker lock(DUChain::lock()); return item->completionProperties() & groupingProperties; } }; } #endif // KDEVPLATFORM_CODECOMPLETIONITEMGROUPER_H diff --git a/kdevplatform/language/codecompletion/codecompletionmodel.h b/kdevplatform/language/codecompletion/codecompletionmodel.h index 8bc71b7144..bee9ceba49 100644 --- a/kdevplatform/language/codecompletion/codecompletionmodel.h +++ b/kdevplatform/language/codecompletion/codecompletionmodel.h @@ -1,150 +1,150 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 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. */ #ifndef KDEVPLATFORM_CODECOMPLETIONMODEL_H #define KDEVPLATFORM_CODECOMPLETIONMODEL_H #include #include #include #include #include #include "../duchain/duchainpointer.h" #include #include "codecompletioncontext.h" #include "codecompletionitem.h" #include #include class QMutex; namespace KDevelop { class DUContext; class Declaration; class CodeCompletionWorker; class CompletionWorkerThread; class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionModel : public KTextEditor::CodeCompletionModel , public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: explicit CodeCompletionModel(QObject* parent); ~CodeCompletionModel() override; ///This MUST be called after the creation of this completion-model. ///If you use use the KDevelop::CodeCompletion helper-class, that one cares about it. virtual void initialize(); ///Entry-point for code-completion. This determines ome settings, clears the model, and then calls completionInvokedInternal for further processing. void completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType invocationType) override; QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount (const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent (const QModelIndex& index) const override; ///Use this to set whether the code-completion widget should wait for this model until it's shown ///This makes sense when the model takes some time but not too much time, to make the UI less flickering and ///annoying. ///The default is false ///@todo Remove this option, make it true by default, and make sure in CodeCompletionWorker that the whole thing cannot break void setForceWaitForModel(bool wait); bool forceWaitForModel(); ///Convenience-storage for use by the inherited completion model void setCompletionContext(const QExplicitlySharedDataPointer& completionContext); QExplicitlySharedDataPointer completionContext() const; ///Convenience-storage for use by the inherited completion model KDevelop::TopDUContextPointer currentTopContext() const; void setCurrentTopContext(const KDevelop::TopDUContextPointer& topContext); ///Whether the completion should be fully detailed. If false, it should be simplifed, so no argument-hints, ///no expanding information, no type-information, etc. bool fullCompletion() const; MatchReaction matchingItem(const QModelIndex& matched) override; QString filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) override; void clear(); ///Returns the tree-element that belogns to the index, or zero QExplicitlySharedDataPointer itemForIndex(const QModelIndex& index) const; Q_SIGNALS: ///Connection from this completion-model into the background worker thread. You should emit this from within completionInvokedInternal. void completionsNeeded(const KDevelop::DUContextPointer& context, const KTextEditor::Cursor& position, KTextEditor::View* view); ///Additional signal that allows directly stepping into the worker-thread, bypassing computeCompletions(..) etc. ///doSpecialProcessing(data) will be executed in the background thread. void doSpecialProcessingInBackground(uint data); protected Q_SLOTS: ///Connection from the background-thread into the model: This is called when the background-thread is ready virtual void foundDeclarations(const QList>& item, const QExplicitlySharedDataPointer& completionContext); protected: ///Eventually override this, determine the context or whatever, and then emit completionsNeeded(..) to continue processing in the background tread. ///The default-implementation does this completely, so if you don't need to do anything special, you can just leave it. virtual void completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType invocationType, const QUrl& url); void executeCompletionItem(KTextEditor::View* view, const KTextEditor::Range& word, const QModelIndex& index) const override; QExplicitlySharedDataPointer m_completionContext; - typedef QPair> DeclarationContextPair; + using DeclarationContextPair = QPair>; QList> m_completionItems; /// Should create a completion-worker. The worker must have no parent object, /// because else its thread-affinity can not be changed. virtual CodeCompletionWorker* createCompletionWorker() = 0; friend class CompletionWorkerThread; CodeCompletionWorker* worker() const; private: bool m_forceWaitForModel; bool m_fullCompletion; QMutex* m_mutex; CompletionWorkerThread* m_thread; QString m_filterString; KDevelop::TopDUContextPointer m_currentTopContext; }; } #endif diff --git a/kdevplatform/language/codegen/astchangeset.h b/kdevplatform/language/codegen/astchangeset.h index d936ea10b3..b21c1b08b5 100644 --- a/kdevplatform/language/codegen/astchangeset.h +++ b/kdevplatform/language/codegen/astchangeset.h @@ -1,246 +1,246 @@ /* Copyright 2008 Hamish Rodda 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 KDEVPLATFORM_ASTCHANGESET_H #define KDEVPLATFORM_ASTCHANGESET_H #include namespace KDevelop { template class AstChangeSet; class AstChange; /** * \short A reference to an existing read-only AST node. * * This class represents an AST node, and allows changes to be planned * for that node. * * \warning you must not create cyclic references. * * \author Hamish Rodda */ template class AstNodeRef { friend class AstChangeSet; public: /// Destructor. virtual ~AstNodeRef() { qDeleteAll(m_changes); if (m_newNode) delete m_node; } - typedef QList AstNodeList; + using AstNodeList = QList; /** * \short Container class for a change to an AST node. * * \author Hamish Rodda */ class AstChange { public: enum ChangeTypes { ListRewrite, ListClear, ItemReplace, ItemMove }; explicit AstChange(ChangeTypes t) : type(t) , newNode(0) , listOffset(-1) , newValue(-1) { } ChangeTypes type; // The index of the item in the node to be changed int nodeIndex; // The new node to occupy this position, if relevant AstNodeRef* newNode; // The list of nodes to occupy this position, if relevant AstNodeList newList; // The position to apply the node(s) in the list, if relevant int listOffset; // The value of the position, if relevant QVariant newValue; }; virtual const AstNode* node() const { return m_newNode ? 0 : m_node; } virtual AstNodeRef* nodeRef() const { return m_nodeRef; } virtual AstNode* newNode() const { return m_newNode ? m_node : 0; } const QList& changes() const { return m_changes; } /// Adds a change to this node reference. Takes ownership of the \a change. AstChange* newChange(AstChange* change) { m_changes.append(change); return change; } /// Removes a change from this node reference, and deletes it. void deleteChange(AstChange* change) { Q_ASSERT(m_changes.contains(change)); m_changes.removeAll(change); delete change; } protected: /// Default constructor. \todo is this needed? AstNodeRef(AstChangeSet* set) : m_changeSet(set) , m_node(0) , m_nodeRef(0) , m_newNode(false) { } /// Constructor. Either takes an existing \a node (\a newNode = false), or a newly created \a node (\a newNode = true) AstNodeRef(AstChangeSet* set, AstNode* node, bool newNode) : m_changeSet(set) , m_node(node) , m_nodeRef(0) , m_newNode(newNode) { } /// Constructor. Takes another node reference. AstNodeRef(AstChangeSet* set, AstNodeRef* original) : m_changeSet(set) , m_node(0) , m_nodeRef(original) , m_newNode(false) { } AstNode* m_nodeChanges; private: AstChangeSet* m_changeSet; AstNode* m_node; AstNodeRef* m_nodeRef; bool m_newNode; QList m_changes; }; /** * \short A set of changes to an AST. * * This class holds a set of all changes to an AST. * * \author Hamish Rodda */ template class AstChangeSet { public: /** * Constructor. * * \param topNode the top node of the read-only Ast to modify, or set to null if creating * a new Ast from scratch. */ AstChangeSet(const AstNode* topNode = 0) : m_topNode(topNode) { } /** * Destructor, deletes all nodes owned by this change set. */ virtual ~AstChangeSet() { qDeleteAll(m_nodeRefs); } /** * Register a new node that you have created to insert at some point in this Ast. * You may modify this node directly. The change set takes ownership, so that * the new node will be deleted when the change set is no longer needed. * * \returns the new node that has been registered. */ AstNodeRef* registerNewNode(AstNode* node) { AstNodeRef* newRef = new AstNodeRef(this, node, true); m_nodeRefs.append(newRef); return newRef; } /** * Create a blank reference to a node. * * The change set takes ownership, so that * the new node will be deleted when the change set is no longer needed. * * \returns the new node reference */ AstNodeRef* registerNewRef(AstNodeRef* ref) { m_nodeRefs.append(ref); return ref; } /** * Copy an existing node (whether from the Ast or from the change set). * * You may then modify this reference, and the modifications will be applied to the node when the change set is finalised. * * \returns a copy of \a source, which you may modify directly. */ AstNodeRef* copyNode(AstNode* source) { AstNodeRef* newRef = new AstNodeRef(this, source, false); m_nodeRefs.append(newRef); return newRef; } private: const AstNode* m_topNode; QList*> m_nodeRefs; }; } #endif // KDEVPLATFORM_ASTCHANGESET_H diff --git a/kdevplatform/language/codegen/codedescription.h b/kdevplatform/language/codegen/codedescription.h index f85636e298..44ba9864b6 100644 --- a/kdevplatform/language/codegen/codedescription.h +++ b/kdevplatform/language/codegen/codedescription.h @@ -1,279 +1,279 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 KDEVPLATFORM_CODEDESCRIPTION_H #define KDEVPLATFORM_CODEDESCRIPTION_H #include #include #include #include #include /** * NOTE: changes in this file will quite probably also require changes * in codedescriptionmetatype.h! */ namespace KDevelop { /** * @brief Represents a variable * * A variable has two main properties: its type and name. **/ struct KDEVPLATFORMLANGUAGE_EXPORT VariableDescription { /** * Creates a variable with no type and no name * **/ VariableDescription(); /** * Creates a variable with type @p type and name @p name * * @param type the type of this variable * @param name the name of this variable **/ VariableDescription(const QString& type, const QString& name); /** * Creates a variable and determines it type and name from the @p declaration * **/ explicit VariableDescription(const DeclarationPointer& declaration); /** * The name of this variable **/ QString name; /** * The type of this variable. * * In weakly typed languages, this field can be empty. **/ QString type; /** * Access specifier, only relevant for class members. * * Not all languages use these, so it can be left empty. **/ QString access; /** * The default value of this variable. */ QString value; }; /** * List of variable descriptions **/ -typedef QVector VariableDescriptionList; +using VariableDescriptionList = QVector; /** * @brief Represents a function * * A function has a name and any number of arguments and return values **/ struct KDEVPLATFORMLANGUAGE_EXPORT FunctionDescription { /** * Creates a function with no name and no arguments * **/ FunctionDescription(); /** * Creates a function with name @p and specified @p arguments and @p returnArguments * * @param name the name of the new function * @param arguments a list of variables that are passed to this function as arguments * @param returnArguments a list of variables that this function returns **/ FunctionDescription(const QString& name, const VariableDescriptionList& arguments, const VariableDescriptionList& returnArguments); /** * Creates a function and determines its properties from the @p declaration * * @param declaration a function declaration **/ explicit FunctionDescription(const DeclarationPointer& declaration); /** * Convenience method, returns the type of the first variable in returnArguments * or an empty string if this function has no return arguments */ QString returnType() const; /** * The name of this function **/ QString name; /** * This function's input arguments **/ QVector arguments; /** * This function's return values **/ QVector returnArguments; /** * Access specifier, only relevant for class members. * * Not all languages use these, so it can be left empty. **/ QString access; /** * Specifies whether this function is a class constructor **/ bool isConstructor : 1; /** * Specifies whether this function is a class destructor */ bool isDestructor : 1; /** * Specifies whether this function is virtual and can be overridden by subclasses **/ bool isVirtual : 1; /** * Specifies whether this function is abstract and needs to be overridden by subclasses **/ bool isAbstract : 1; /** * Specifies whether this function overrides a virtual method of a base class **/ bool isOverriding : 1; /** * Specifies whether this function is final and cannot be overridden by subclasses **/ bool isFinal : 1; /** * Specifies whether this function is static and can be called without a class instance **/ bool isStatic : 1; /** * Specifies whether this function is a slot **/ bool isSlot : 1; /** * Specifies whether this function is a signal **/ bool isSignal : 1; /** * Specifies whether this function is constant **/ bool isConst : 1; }; /** * List of function descriptions **/ -typedef QVector FunctionDescriptionList; +using FunctionDescriptionList = QVector; /** * Description of an inheritance relation. **/ struct KDEVPLATFORMLANGUAGE_EXPORT InheritanceDescription { /** * @brief The mode of this inheritance. * * For C++ classes, mode string are the same as access specifiers (public, protected, private). * In other languages, the mode is used to differentiate between extends/implements * or other possible inheritance types. * * Some languages do not recognise distinct inheritance modes at all. **/ QString inheritanceMode; /** * The name of the base class **/ QString baseType; }; /** * List of inheritance descriptions **/ -typedef QVector InheritanceDescriptionList; +using InheritanceDescriptionList = QVector; /** * @brief Represents a class * * A class descriptions stores its name, its member variables and functions, as well as its superclasses and inheritance types. **/ struct KDEVPLATFORMLANGUAGE_EXPORT ClassDescription { /** * Creates an empty class * **/ ClassDescription(); /** * Creates an empty class named @p name * * @param name the name of the new class **/ explicit ClassDescription(const QString& name); /** * The name of this class **/ QString name; /** * List of base classes (classes from which this one inherits) as well as inheritance types **/ InheritanceDescriptionList baseClasses; /** * List of all member variables in this class **/ VariableDescriptionList members; /** * List of all member functions (methods) in this class **/ FunctionDescriptionList methods; }; namespace CodeDescription { template QVariant toVariantList(const QVector& list) { QVariantList ret; ret.reserve(list.size()); for (const T& t : list) { ret << QVariant::fromValue(t); } return QVariant::fromValue(ret); } } } Q_DECLARE_TYPEINFO(KDevelop::VariableDescription, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::FunctionDescription, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::InheritanceDescription, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::ClassDescription, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::VariableDescription) Q_DECLARE_METATYPE(KDevelop::VariableDescriptionList) Q_DECLARE_METATYPE(KDevelop::FunctionDescription) Q_DECLARE_METATYPE(KDevelop::FunctionDescriptionList) Q_DECLARE_METATYPE(KDevelop::InheritanceDescription) Q_DECLARE_METATYPE(KDevelop::InheritanceDescriptionList) Q_DECLARE_METATYPE(KDevelop::ClassDescription) #endif // KDEVPLATFORM_CODEDESCRIPTION_H diff --git a/kdevplatform/language/codegen/codegenerator.h b/kdevplatform/language/codegen/codegenerator.h index 2de4a4aee0..347124010a 100644 --- a/kdevplatform/language/codegen/codegenerator.h +++ b/kdevplatform/language/codegen/codegenerator.h @@ -1,250 +1,250 @@ /* 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 KDEVPLATFORM_CODEGENERATOR_H #define KDEVPLATFORM_CODEGENERATOR_H #include #include "../duchain/topducontext.h" #include "../duchain/duchain.h" #include "language/interfaces/iastcontainer.h" #include namespace KDevelop { template class AstChangeSet; class DUContext; class DUChainChangeSet; class DocumentChangeSet; class DocumentRange; /** * \short Base class for generic code generators * * CodeGeneratorBase provides an api for a step-by-step process to * create and/or refactor code. * * This class should be used as a superclass only when du-chain level * changes are made, since this level knows nothing about the * language-specific AST. For more complex changes that require knowledge * about the language AST, use CodeGenerator * * \see Refactoring * \see CodeGenerator * \author Hamish Rodda */ class KDEVPLATFORMLANGUAGE_EXPORT CodeGeneratorBase { public: CodeGeneratorBase(); virtual ~CodeGeneratorBase(); enum State { Precondition, UserInput, Processing, Review, Finished }; /** * Check whether the preconditions of this generation are met at the given \a context and * \a position. * \returns true if conditions are met and the generator can progress, otherwise false if * the conditions are not met. Use setErrorText to specify the nature of the Error. */ virtual bool checkPreconditions(DUContext* context, const DocumentRange& position) = 0; /** * Gather information required from the user for this generator. * * \returns true if all of the information is retrieved, otherwise false, Use setErrorText * to specify the nature of the Error. */ virtual bool gatherInformation() = 0; /** * Do final condition checking and perform the code generation. * * \returns true if code generation was successful, false otherwise. Use setErrorText to * specify the nature of the Error. */ virtual bool process() = 0; const QString& errorText() const; // Implementation from kJob bool execute(); /** * @brief Indicates that this generation should not expect interaction with the user/ * Most probable scenarios are: Testing, and a generator that is being used by another one * @param context If not NULL, the custom context to use, instead of user selection * @param range If not NULL, the cursom range to use instead of user selection * @note If this function is called, then gather information will not be called. * Derived classes should provide an alternative way of setting up the generator. */ void autoGenerate(DUContext* context, const DocumentRange* range); /** * \return The Document Change set to add a single Change, it is more addicient than creating a local DocumentChangeSet and merging it */ DocumentChangeSet& documentChangeSet(); protected: /** * Generate text edits from duchain change set. * This generator now owns the changeset, and will delete it. * * You may call this method multiple times to edit different files. */ void addChangeSet(DUChainChangeSet* duChainChange); void addChangeSet(DocumentChangeSet& docChangeSet); /** * Accessor for KJob's KJob::setErrorText. */ void setErrorText(const QString& error); /** * Inform the derived class if this generation is being performed without user interaction */ bool autoGeneration() const; /** * Clean up all the change sets that this generator is in charge of */ void clearChangeSets(); private: bool displayChanges(); private: const QScopedPointer d; }; /** * \brief Base class for Ast aware code generators * * This class provides convenience for adding AstChangeSet, storing * the IAstContainer from the TopDUContext, and in general managing * Code generators that manipulate the AST * * \see CodeGeneratorBase * \author Ramón Zarazúa */ template class CodeGenerator : public CodeGeneratorBase { public: ~CodeGenerator() { clearChangeSets(); } protected: /// Convenience definition of the TopAstNode that is contained by this AstContainer - typedef typename AstContainer::TopAstNode TopAstNode; - typedef AstChangeSet LanguageChangeSet; + using TopAstNode = typename AstContainer::TopAstNode ; + using LanguageChangeSet = AstChangeSet; /** * Query an AST of a particular file */ TopAstNode* ast(const IndexedString& file) { return astContainer(file)->topAstNode(); } TopAstNode* ast(const TopDUContext& context) { return astContainer(context)->topAstNode(); } typename AstContainer::Ptr astContainer(const IndexedString& file) { if (!m_AstContainers.contains(file)) { qCDebug(LANGUAGE) << "Ast requested for: " << file.str(); TopDUContext* context = DUChain::self()->waitForUpdate(file, KDevelop::TopDUContext::AST).data(); Q_ASSERT(context); m_AstContainers[file] = AstContainer::Ptr::template staticCast(context->ast()); } return m_AstContainers[file]; } typename AstContainer::Ptr astContainer(const TopDUContext& context) { return astContainer(context.url()); } /** * Generate text edits from duchain / ast change set. * * You may call this method multiple times to edit different files. */ void addChangeSet(DUChainChangeSet* duChainChange) { CodeGeneratorBase::addChangeSet(duChainChange); } void addChangeSet(DocumentChangeSet& doc) { CodeGeneratorBase::addChangeSet(doc); } /** * Generate text edits from duchain / ast change set. * * You may call this method multiple times to edit different files. */ void addChangeSet(LanguageChangeSet* astChange); void clearChangeSets() { CodeGeneratorBase::clearChangeSets(); } /** * Inform the derived class if this generation is being performed without user interaction */ bool autoGeneration() const { return CodeGeneratorBase::autoGeneration(); } /** * Accessor for KJob's KJob::setErrorText. */ void setErrorText(const QString& error) { CodeGeneratorBase::setErrorText(error); } private: - typedef QMap AstContainerMap; + using AstContainerMap = QMap; AstContainerMap m_AstContainers; }; } #endif // KDEVPLATFORM_CODEGENERATOR_H diff --git a/kdevplatform/language/codegen/coderepresentation.h b/kdevplatform/language/codegen/coderepresentation.h index f4dfde3b5c..2ae2addca0 100644 --- a/kdevplatform/language/codegen/coderepresentation.h +++ b/kdevplatform/language/codegen/coderepresentation.h @@ -1,197 +1,197 @@ /* 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. */ #ifndef KDEVPLATFORM_CODEREPRESENTATION_H #define KDEVPLATFORM_CODEREPRESENTATION_H #include #include #include #include #include class QString; namespace KTextEditor { class Range; } namespace KDevelop { struct KDevEditingTransaction; class IndexedString; //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 struct EditorDisableReplaceTabs { explicit EditorDisableReplaceTabs(KTextEditor::Document* document) : m_iface(qobject_cast( document)) , m_count(0) { ++m_count; if (m_count > 1) return; if (m_iface) { m_oldReplaceTabs = m_iface->configValue(configKey()); m_iface->setConfigValue(configKey(), false); } } ~EditorDisableReplaceTabs() { --m_count; if (m_count > 0) return; Q_ASSERT(m_count == 0); if (m_iface) m_iface->setConfigValue(configKey(), m_oldReplaceTabs); } inline QString configKey() const { return QStringLiteral("replace-tabs"); } KTextEditor::ConfigInterface* m_iface; int m_count; QVariant m_oldReplaceTabs; }; struct KDevEditingTransaction { explicit KDevEditingTransaction(KTextEditor::Document* document) : edit(document) , disableReplaceTabs(document) {} // NOTE: It's important to close the transaction first and only then destroy the EditorDisableReplaceTabs. Otherwise we hit asserts in KTextEditor. KTextEditor::Document::EditingTransaction edit; EditorDisableReplaceTabs disableReplaceTabs; - typedef std::unique_ptr Ptr; + using Ptr = std::unique_ptr; }; /** * Allows getting code-lines conveniently, either through an open editor, or from a disk-loaded file. */ class KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation : public QSharedData { public: virtual ~CodeRepresentation() { } virtual QString line(int line) const = 0; virtual int lines() const = 0; virtual QString text() const = 0; virtual QString rangeText(const KTextEditor::Range& range) const; /** * Search for the given identifier in the document, and returns all ranges * where it was found. * @param identifier The identifier to search for * @param surroundedByBoundary Whether only matches that are surrounded by typical word-boundaries * should be acceded. Everything except letters, numbers, and the _ character * counts as word boundary. * */ virtual QVector grep(const QString& identifier, bool surroundedByBoundary = true) const = 0; /** * Overwrites the text in the file with the new given one * * @return true on success */ virtual bool setText(const QString&) = 0; /** @return true if this representation represents an actual file on disk */ virtual bool fileExists() const = 0; /** * Can be used for example from tests to disallow on-disk changes. When such a change is done, an assertion is triggered. * You should enable this within tests, unless you really want to work on the disk. */ static void setDiskChangesForbidden(bool changesForbidden); /** * Returns the specified name as a url for aritificial source code * suitable for code being inserted into the parser */ static QString artificialPath(const QString& name); - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; }; class KDEVPLATFORMLANGUAGE_EXPORT DynamicCodeRepresentation : public CodeRepresentation { public: /** Used to group edit-history together. Call this optionally before a bunch * of replace() calls, to group them together. */ virtual KDevEditingTransaction::Ptr makeEditTransaction() = 0; virtual bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText = false) = 0; - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; }; /** * Creates a code-representation for the given url, that allows conveniently accessing its data. Returns zero on failure. */ KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& url); /** * @return true if an artificial code representation already exists for the specified URL */ KDEVPLATFORMLANGUAGE_EXPORT bool artificialCodeRepresentationExists(const IndexedString& url); /** * Allows inserting artificial source-code into the code-representation and parsing framework. * The source-code logically represents a file. * * The artificial code is automatically removed when the object is destroyed. */ class KDEVPLATFORMLANGUAGE_EXPORT InsertArtificialCodeRepresentation : public QSharedData { public: /** * Inserts an artifial source-code representation with filename @p file and the contents @p text * If @p file is not an absolute path or url, it will be made absolute using the CodeRepresentation::artifialUrl() * function, while ensuring that the name is unique. */ InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text); ~InsertArtificialCodeRepresentation(); void setText(const QString& text); QString text() const; /** * Returns the file-name for this code-representation. */ IndexedString file(); private: InsertArtificialCodeRepresentation(const InsertArtificialCodeRepresentation&); InsertArtificialCodeRepresentation& operator=(const InsertArtificialCodeRepresentation&); IndexedString m_file; }; -typedef QExplicitlySharedDataPointer InsertArtificialCodeRepresentationPointer; +using InsertArtificialCodeRepresentationPointer = QExplicitlySharedDataPointer; } #endif diff --git a/kdevplatform/language/codegen/documentchangeset.cpp b/kdevplatform/language/codegen/documentchangeset.cpp index bcbfe42e0c..546547a339 100644 --- a/kdevplatform/language/codegen/documentchangeset.cpp +++ b/kdevplatform/language/codegen/documentchangeset.cpp @@ -1,593 +1,593 @@ /* 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 #include #include #include #include #include #include namespace KDevelop { -typedef QList ChangesList; -typedef QHash ChangesHash; +using ChangesList = QList; +using ChangesHash = QHash; class DocumentChangeSetPrivate { public: DocumentChangeSet::ReplacementPolicy replacePolicy; DocumentChangeSet::FormatPolicy formatPolicy; DocumentChangeSet::DUChainUpdateHandling updatePolicy; DocumentChangeSet::ActivationPolicy activationPolicy; ChangesHash changes; QHash documentsRename; DocumentChangeSet::ChangeResult addChange(const DocumentChangePointer& change); DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList); DocumentChangeSet::ChangeResult generateNewText(const IndexedString& file, ChangesList& sortedChanges, const CodeRepresentation* repr, QString& output); DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString& file, ChangesList& filteredChanges); void formatChanges(); void updateFiles(); }; // Simple helpers 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(); } inline bool duplicateChanges(const DocumentChangePointer& previous, const 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)); } inline QString rangeText(const KTextEditor::Range& range, const QStringList& textLines) { QStringList ret; ret.reserve(range.end().line() - range.start().line() + 1); for (int line = range.start().line(); line <= range.end().line(); ++line) { const QString lineText = textLines.at(line); int startColumn = 0; int endColumn = lineText.length(); if (line == range.start().line()) { startColumn = range.start().column(); } if (line == range.end().line()) { endColumn = range.end().column(); } ret << lineText.mid(startColumn, endColumn - startColumn); } return ret.join(QLatin1Char('\n')); } // need to have it as otherwise the arguments can exceed the maximum of 10 static QString printRange(const KTextEditor::Range& r) { return i18nc("text range line:column->line:column", "%1:%2->%3:%4", r.start().line(), r.start().column(), r.end().line(), r.end().column()); } } 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 DocumentChangeSet& rhs) { *d = *rhs.d; return *this; } DocumentChangeSet::~DocumentChangeSet() = default; DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChange& change) { return d->addChange(DocumentChangePointer(new DocumentChange(change))); } DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChangePointer& change) { return d->addChange(change); } DocumentChangeSet::ChangeResult DocumentChangeSet::addDocumentRenameChange(const IndexedString& oldFile, const IndexedString& newname) { d->documentsRename.insert(oldFile, newname); return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(const DocumentChangePointer& change) { changes[change->m_document].append(change); return DocumentChangeSet::ChangeResult::successfulResult(); } 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; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges() { QUrl oldActiveDoc; if (IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { oldActiveDoc = activeDoc->url(); } QList allFiles; const auto changedFiles = d->documentsRename.keys().toSet() + d->changes.keys().toSet(); allFiles.reserve(changedFiles.size()); for (const IndexedString& file : changedFiles) { allFiles << file.toUrl(); } if (!KDevelop::ensureWritable(allFiles)) { return ChangeResult(QStringLiteral("some affected files are not writable")); } // rename files QHash::const_iterator it = d->documentsRename.constBegin(); for (; it != d->documentsRename.constEnd(); ++it) { QUrl url = it.key().toUrl(); IProject* p = ICore::self()->projectController()->findProjectForUrl(url); if (p) { QList files = p->filesForPath(it.key()); if (!files.isEmpty()) { ProjectBaseItem::RenameStatus renamed = files.first()->rename(it.value().str()); if (renamed == ProjectBaseItem::RenameOk) { const QUrl newUrl = Path(Path(url).parent(), it.value().str()).toUrl(); if (url == oldActiveDoc) { oldActiveDoc = newUrl; } IndexedString idxNewDoc(newUrl); // ensure changes operate on new file name ChangesHash::iterator iter = d->changes.find(it.key()); if (iter != d->changes.end()) { // copy changes ChangesList value = iter.value(); // remove old entry d->changes.erase(iter); // adapt to new url ChangesList::iterator itChange = value.begin(); ChangesList::iterator itEnd = value.end(); for (; itChange != itEnd; ++itChange) { (*itChange)->m_document = idxNewDoc; } d->changes[idxNewDoc] = value; } } else { ///FIXME: share code with project manager for the error code string representation return ChangeResult(i18n("Could not rename '%1' to '%2'", url.toDisplayString(QUrl::PreferLocalFile), it.value().str())); } } else { //TODO: do it outside the project management? qCWarning(LANGUAGE) << "tried to rename file not tracked by project - not implemented"; } } else { qCWarning(LANGUAGE) << "tried to rename a file outside of a project - not implemented"; } } QMap codeRepresentations; QMap newTexts; ChangesHash filteredSortedChanges; ChangeResult result = ChangeResult::successfulResult(); const QList files(d->changes.keys()); for (const IndexedString& file : files) { CodeRepresentation::Ptr repr = createCodeRepresentation(file); if (!repr) { return ChangeResult(QStringLiteral("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 for (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; } } d->updateFiles(); if (d->activationPolicy == Activate) { for (const IndexedString& file : files) { ICore::self()->documentController()->openDocument(file.toUrl()); } } // ensure the old document is still activated if (oldActiveDoc.isValid()) { ICore::self()->documentController()->openDocument(oldActiveDoc); } return result; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation* repr, const QString& newText, const ChangesList& sortedChangesList) { auto* dynamic = dynamic_cast(repr); if (dynamic) { auto transaction = dynamic->makeEditTransaction(); //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, change.m_oldText, change.m_newText, change.m_ignoreOldText)) { QString warningString = i18nc( "Inconsistent change in between , found (encountered ) -> ", "Inconsistent change in %1 between %2, found %3 (encountered \"%4\") -> \"%5\"", change.m_document.str(), printRange(change.m_range), change.m_oldText, dynamic->rangeText(change.m_range), change.m_newText); if (replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else if (replacePolicy == DocumentChangeSet::StopOnFailedChange) { return DocumentChangeSet::ChangeResult(warningString); } //If set to ignore failed changes just continue with the others } } return DocumentChangeSet::ChangeResult::successfulResult(); } //For files on disk if (!repr->setText(newText)) { QString warningString = i18n("Could not replace text in the document: %1", sortedChangesList.begin()->data()->m_document.str()); if (replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } return DocumentChangeSet::ChangeResult(warningString); } return DocumentChangeSet::ChangeResult::successfulResult(); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString& file, ChangesList& sortedChanges, const CodeRepresentation* repr, QString& output) { ISourceFormatter* formatter = nullptr; if (ICore::self()) { formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl()); } //Create the actual new modified file QStringList textLines = repr->text().split(QLatin1Char('\n')); QUrl url = file.toUrl(); QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); QVector removedLines; 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 = rangeText(change.m_range, textLines)) == 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(QLatin1Char( '\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(QLatin1Char('\n')).mid( change.m_range.end().column()); if (formatter && (formatPolicy == DocumentChangeSet::AutoFormatChanges || formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation)) { QString oldNewText = change.m_newText; change.m_newText = formatter->formatSource(change.m_newText, url, mime, leftContext, rightContext); if (formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation) { // Reproduce the previous indentation QStringList oldLines = oldNewText.split(QLatin1Char('\n')); QStringList newLines = change.m_newText.split(QLatin1Char('\n')); if (oldLines.size() == newLines.size()) { for (int line = 0; line < newLines.size(); ++line) { // Keep the previous indentation QString oldIndentation; for (int a = 0; a < oldLines[line].size(); ++a) { if (oldLines[line][a].isSpace()) { oldIndentation.append(oldLines[line][a]); } else { break; } } int newIndentationLength = 0; for (int a = 0; a < newLines[line].size(); ++a) { if (newLines[line][a].isSpace()) { newIndentationLength = a; } else { break; } } newLines[line].replace(0, newIndentationLength, oldIndentation); } change.m_newText = newLines.join(QLatin1Char('\n')); } else { qCDebug(LANGUAGE) << "Cannot keep the indentation because the line count has changed" << oldNewText; } } } QString& line = textLines[change.m_range.start().line()]; if (change.m_range.start().line() == change.m_range.end().line()) { // simply replace existing line content line.replace(change.m_range.start().column(), change.m_range.end().column() - change.m_range.start().column(), change.m_newText); } else { // replace first line contents line.replace(change.m_range.start().column(), line.length() - change.m_range.start().column(), change.m_newText); // null other lines and remember for deletion const int firstLine = change.m_range.start().line() + 1; const int lastLine = change.m_range.end().line(); removedLines.reserve(removedLines.size() + lastLine - firstLine + 1); for (int i = firstLine; i <= lastLine; ++i) { textLines[i].clear(); removedLines << i; } } } else { QString warningString = i18nc("Inconsistent change in at " " = (encountered ) -> ", "Inconsistent change in %1 at %2" " = \"%3\"(encountered \"%4\") -> \"%5\"", file.str(), printRange(change.m_range), change.m_oldText, encountered, change.m_newText); if (replacePolicy == DocumentChangeSet::IgnoreFailedChange) { //Just don't do the replacement } else if (replacePolicy == DocumentChangeSet::WarnOnFailedChange) { qCWarning(LANGUAGE) << warningString; } else { return DocumentChangeSet::ChangeResult(warningString, sortedChanges[pos]); } } } if (!removedLines.isEmpty()) { int offset = 0; std::sort(removedLines.begin(), removedLines.end()); foreach (int l, removedLines) { textLines.removeAt(l - offset); ++offset; } } output = textLines.join(QLatin1Char('\n')); return DocumentChangeSet::ChangeResult::successfulResult(); } //Removes all duplicate changes for a single file, and then returns (via filteredChanges) the filtered duplicates DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::removeDuplicates(const IndexedString& file, ChangesList& filteredChanges) { - typedef QMultiMap ChangesMap; + using ChangesMap = QMultiMap; ChangesMap sortedChanges; foreach (const DocumentChangePointer& change, changes[file]) { sortedChanges.insert(change->m_range.end(), change); } //Remove duplicates ChangesMap::iterator previous = sortedChanges.begin(); for (ChangesMap::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) { qCDebug(LANGUAGE) << "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) { qCDebug(LANGUAGE) << "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( i18nc("Inconsistent change-request at :" "intersecting changes: " " -> @ & " " -> @", "Inconsistent change-request at %1; " "intersecting changes: " "\"%2\"->\"%3\"@%4 & \"%5\"->\"%6\"@%7 ", file.str(), (*previous)->m_oldText, (*previous)->m_newText, printRange((*previous)->m_range), (*it)->m_oldText, (*it)->m_newText, printRange((*it)->m_range))); } } previous = it; ++it; } filteredChanges = sortedChanges.values(); return DocumentChangeSet::ChangeResult::successfulResult(); } 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 (IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(activeDoc->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); } } // Eventually update _all_ affected files foreach (const IndexedString& file, changes.keys()) { if (!file.toUrl().isValid()) { qCWarning(LANGUAGE) << "Trying to apply changes to an invalid document"; continue; } ICore::self()->languageController()->backgroundParser()->addDocument(file); } } } } diff --git a/kdevplatform/language/codegen/documentchangeset.h b/kdevplatform/language/codegen/documentchangeset.h index 4cb44b4671..df2813962b 100644 --- a/kdevplatform/language/codegen/documentchangeset.h +++ b/kdevplatform/language/codegen/documentchangeset.h @@ -1,154 +1,154 @@ /* 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. */ #ifndef KDEVPLATFORM_DOCUMENTCHANGESET_H #define KDEVPLATFORM_DOCUMENTCHANGESET_H #include #include #include #include #include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT DocumentChange : public QSharedData { public: DocumentChange(const IndexedString& document, const KTextEditor::Range& range, const QString& oldText, const QString& newText) : m_document(document) , m_range(range) , m_oldText(oldText) , m_newText(newText) , m_ignoreOldText(false) { //Clean the URL, so we don't get the same file be stored as a different one QUrl url = m_document.toUrl(); m_document = IndexedString(url.adjusted(QUrl::NormalizePathSegments)); } IndexedString m_document; KTextEditor::Range m_range; QString m_oldText; QString m_newText; bool m_ignoreOldText; //Set this to disable the verification of m_oldText. This can be used to overwrite arbitrary text, but is dangerous! }; -typedef QExplicitlySharedDataPointer DocumentChangePointer; +using DocumentChangePointer = QExplicitlySharedDataPointer; /** * Object representing an arbitrary set of changes to an arbitrary set of files that can be applied atomically. */ class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeSet { public: DocumentChangeSet(); ~DocumentChangeSet(); DocumentChangeSet(const DocumentChangeSet& rhs); DocumentChangeSet& operator=(const DocumentChangeSet& rhs); //Returns true on success class ChangeResult { public: explicit ChangeResult(const QString& failureReason, const DocumentChangePointer& reasonChange = DocumentChangePointer()) : ChangeResult(failureReason, reasonChange, false) {} static ChangeResult successfulResult() { return ChangeResult({}, {}, true); } operator bool() const { return m_success; } /// Reason why the change failed QString m_failureReason; /// Specific change that caused the problem (might be 0) DocumentChangePointer m_reasonChange; bool m_success; private: explicit ChangeResult(const QString& failureReason, const DocumentChangePointer& reasonChange, bool success) : m_failureReason(failureReason) , m_reasonChange(reasonChange) , m_success(success) {} }; /// Add an individual local change to this change-set. ChangeResult addChange(const DocumentChange& change); ChangeResult addChange(const DocumentChangePointer& change); ///given a file @p oldFile, rename it to the @p newname ChangeResult addDocumentRenameChange(const IndexedString& oldFile, const IndexedString& newname); enum ReplacementPolicy { IgnoreFailedChange,///If this is given, all changes that could not be applied are simply ignored WarnOnFailedChange,///If this is given to applyAllChanges, a warning is given when a change could not be applied, ///but following changes are applied, and success is returned. StopOnFailedChange ///If this is given to applyAllChanges, then all replacements are reverted and an error returned on problems (default) }; ///@param policy What should be done when a change could not be applied? void setReplacementPolicy(ReplacementPolicy policy); enum FormatPolicy { NoAutoFormat, ///If this option is given, no automatic formatting is applied AutoFormatChanges, ///If this option is given, all changes are automatically reformatted using the formatter plugin for the mime type (default) AutoFormatChangesKeepIndentation ///Same as AutoFormatChanges, except that the indentation of inserted lines is kept equal }; ///@param policy How the changed text should be formatted. The default is AutoFormatChanges. void setFormatPolicy(FormatPolicy policy); enum DUChainUpdateHandling { NoUpdate, ///No updates will be scheduled SimpleUpdate ///The changed documents will be added to the background parser, plus all documents that are currently open and recursively import those documents (default) //FullUpdate ///All documents in all open projects that recursively import any of the changed documents will be updated }; ///@param policy Whether a duchain update should be triggered for all affected documents void setUpdateHandling(DUChainUpdateHandling policy); enum ActivationPolicy { Activate, ///The affected files will be activated DoNotActivate ///The affected files will not be activated (default) }; ///@param policy Whether the affected documents should be activated when the change is applied void setActivationPolicy(ActivationPolicy policy); /// Apply all the changes registered in this changeset to the actual files ChangeResult applyAllChanges(); private: const QScopedPointer d; }; } #endif diff --git a/kdevplatform/language/codegen/duchainchangeset.h b/kdevplatform/language/codegen/duchainchangeset.h index ceb428a590..beceddd99c 100644 --- a/kdevplatform/language/codegen/duchainchangeset.h +++ b/kdevplatform/language/codegen/duchainchangeset.h @@ -1,258 +1,258 @@ /* Copyright 2008 Hamish Rodda 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 KDEVPLATFORM_DUCHAINCHANGESET_H #define KDEVPLATFORM_DUCHAINCHANGESET_H #include #include "../duchain/identifier.h" #include "../duchain/topducontext.h" #include "../duchain/declaration.h" namespace KDevelop { class DUChainChangeSet; class DUChainChange; class DUChainBase; class DUContextRef; template class AstNodeRef; /** * \short A reference to an existing read-only DUChain object. * * This class represents a duchain object (eg, a KDevelop::DUContext), * and allows changes to be planned for that object. * * \todo Evaluate usefulness of changing child contexts - needed? * * \warning you must not create cyclic references. * \author Hamish Rodda */ class KDEVPLATFORMLANGUAGE_EXPORT DUChainRef { friend class DUChainChangeSet; public: /*virtual ~DUChainRef(); virtual const DUChainBase* object() const; virtual const DUContext* context() const; virtual const Declaration* declaration() const; virtual DUChainRef* objectRef() const; virtual DUChainBase* newObject() const; virtual DUContext* newContext() const; virtual Declaration* newDeclaration() const; const QList& changes() const; /// Rename this object, if applicable void renameObject(const QualifiedIdentifier& newIdentifier); /// Change the access policy void setAccessPolicy(Declaration::AccessPolicy newPolicy); void deleteChildContext(DUContext* child); void insertChildContext(DUContextRef* newChild); void deleteDeclaration(Declaration* declaration); void insertDeclaration(Declaration* declaration, DUChainBase* afterObject); void appendDeclaration(Declaration* declaration); AbstractType::Ptr currentType() const; void changeType(AbstractType::Ptr newType); */ /** * Rewrite the AST which created this duchain object. Eg: * - for declarations, the entire declaration. * - for contexts, the contents of the context. * - for types, the type declaration. * * \returns a reference to the AST which represents this object as it currently * exists (after any existing duchain changes are applied). Changes * made to the AST will be applied along with the duchain change set. */ /* template AstNodeRef * rewriteAst(); /// Removes a change from this object reference, and deletes it. void deleteChange(DUChainChange* change); protected: /// Constructor. Either takes an existing \a object (\a newObject = false), or a newly created \a object (\a newObject = true) DUChainRef(DUChainChangeSet* set, DUChainBase* object, bool newObject); /// Constructor. Takes another object reference. DUChainRef(DUChainChangeSet* set, DUChainRef* original); /// Adds a change to this object reference. Takes ownership of the \a change. DUChainChange* addChange(DUChainChange* change); private: DUChainChangeSet* m_changeSet; DUChainBase* m_object; DUChainRef* m_objectRef; bool m_newObject; QList m_changes;*/ }; -typedef QList DUChainBaseList; +using DUChainBaseList = QList; /** * \short Container class for a change to a duchain object. * * \author Hamish Rodda */ class KDEVPLATFORMLANGUAGE_EXPORT DUChainChange { public: enum ChangeTypes { Rename, ListInsert, ListRemove, ListClear, ItemReplace, ItemMove, TypeChange } type; explicit DUChainChange(ChangeTypes t) : type(t) {} enum ItemToChange { ContextChildren, ContextDeclarations } itemToChange; /// New local identifier (eg. for contexts, the new DUContext::localScopeIdentifier() ) QualifiedIdentifier newIdentifier; /// The new object to occupy this position, if relevant DUChainRef* newObject; /// The list of objects to occupy this position, if relevant DUChainBaseList newList; /// The position to apply the object(s) in the list, if relevant int listOffset; /// The value of the position, if relevant QVariant newValue; AbstractType::Ptr newType; }; /** * \short A set of changes to a DUChain. * * This class holds a set of all changes to a DU Chain, and provides an interface * to convenience functions provided by the specific language support involved. * * \author Hamish Rodda */ class KDEVPLATFORMLANGUAGE_EXPORT DUChainChangeSet { public: /** * Constructor. * * \param topContext the top context of the read-only DUChain to modify, or set to null if creating * a new DUChain from scratch. */ explicit DUChainChangeSet(const ReferencedTopDUContext& topContext); /** * Destructor, deletes all objects, references and changes owned by this change set. */ virtual ~DUChainChangeSet(); /** * Create a new declaration to be managed by this change set. * * \returns the new declaration reference */ virtual DUChainRef* newDeclaration() = 0; /** * Create a new class to be managed by this change set. * * \returns the new declaration reference */ virtual DUChainRef* newClass() = 0; /** * Create a new function to be managed by this change set. * * \returns the new declaration reference */ virtual DUChainRef* newFunction() = 0; /** * Copy an existing object from a change set. * * This change set takes ownership, so that * the new object will be deleted when the change set is no longer needed. * * \returns the new object reference */ DUChainRef* copyRef(DUChainRef* ref); /** * Merge another changeset with this one. This changeset * takes ownership of all the objects in the other changeset. * After the merge, the merged object becomes empty. * * Both changesets must reference the same TopDuContext. */ DUChainChangeSet& operator<<(DUChainChangeSet& rhs); /** * Produce a reference to an existing object in this chain, and replace the * object with the reference so that modifications to the reference are already * integrated into the change set. * * You may then modify this reference, and the modifications will be applied * to the chain when the change set is finalised. * * \returns a reference to \a source, which you may modify directly. */ DUChainRef* modifyObject(DUChainBase* source); /** * Copy an existing object (whether from the DUChain or from the change set). * Does not insert the object into the chain. * * You may then modify this reference, and the modifications will be applied to the object when the change set is finalised. * * \returns a copy of \a source, which you may modify directly. */ DUChainRef* copyObject(DUChainBase* source); /** * Retrieve the list of object references and changes. */ QList objectRefs() const; const ReferencedTopDUContext& topDuContext() const; private: ReferencedTopDUContext m_topContext; QList m_objectRefs; }; } #endif // KDEVPLATFORM_DUCHAINCHANGESET_H diff --git a/kdevplatform/language/codegen/sourcefiletemplate.cpp b/kdevplatform/language/codegen/sourcefiletemplate.cpp index 0aa7528d33..ef73b6c82f 100644 --- a/kdevplatform/language/codegen/sourcefiletemplate.cpp +++ b/kdevplatform/language/codegen/sourcefiletemplate.cpp @@ -1,336 +1,336 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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 "sourcefiletemplate.h" #include "templaterenderer.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; -typedef SourceFileTemplate::ConfigOption ConfigOption; +using ConfigOption = SourceFileTemplate::ConfigOption; class KDevelop::SourceFileTemplatePrivate { public: KArchive* archive; QString descriptionFileName; QStringList searchLocations; ConfigOption readEntry(const QDomElement& element, TemplateRenderer* renderer); }; ConfigOption SourceFileTemplatePrivate::readEntry(const QDomElement& element, TemplateRenderer* renderer) { ConfigOption entry; entry.name = element.attribute(QStringLiteral("name")); entry.type = element.attribute(QStringLiteral("type"), QStringLiteral("String")); bool isDefaultValueSet = false; for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { QString tag = e.tagName(); if (tag == QLatin1String("label")) { entry.label = e.text(); } else if (tag == QLatin1String("tooltip")) { entry.label = e.text(); } else if (tag == QLatin1String("whatsthis")) { entry.label = e.text(); } else if (tag == QLatin1String("min")) { entry.minValue = e.text(); } else if (tag == QLatin1String("max")) { entry.maxValue = e.text(); } else if (tag == QLatin1String("default")) { entry.value = renderer->render(e.text(), entry.name); isDefaultValueSet = true; } else if (tag == QLatin1String("choices")) { QStringList values; QDomNodeList choices = element.elementsByTagName(QStringLiteral("choice")); values.reserve(choices.size()); for (int j = 0; j < choices.size(); ++j) { QDomElement choiceElement = choices.at(j).toElement(); values << choiceElement.attribute(QStringLiteral("name")); } Q_ASSERT(!values.isEmpty()); if (values.isEmpty()) { qCWarning(LANGUAGE) << "Entry " << entry.name << "has an enum without any choices"; } entry.values = values; } } qCDebug(LANGUAGE) << "Read entry" << entry.name << "with default value" << entry.value; // preset value for enum if needed if (!entry.values.isEmpty()) { if (isDefaultValueSet) { const bool isSaneDefaultValue = entry.values.contains(entry.value.toString()); Q_ASSERT(isSaneDefaultValue); if (!isSaneDefaultValue) { qCWarning(LANGUAGE) << "Default value" << entry.value << "not in enum" << entry.values; entry.value = entry.values.at(0); } } else { entry.value = entry.values.at(0); } } return entry; } SourceFileTemplate::SourceFileTemplate (const QString& templateDescription) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; setTemplateDescription(templateDescription); } SourceFileTemplate::SourceFileTemplate() : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; } SourceFileTemplate::SourceFileTemplate (const SourceFileTemplate& other) : d(new KDevelop::SourceFileTemplatePrivate) { d->archive = nullptr; *this = other; } SourceFileTemplate::~SourceFileTemplate() { delete d->archive; } SourceFileTemplate& SourceFileTemplate::operator=(const SourceFileTemplate& other) { if (other.d == d) { return *this; } delete d->archive; if (other.d->archive) { if (other.d->archive->fileName().endsWith(QLatin1String(".zip"))) { d->archive = new KZip(other.d->archive->fileName()); } else { d->archive = new KTar(other.d->archive->fileName()); } d->archive->open(QIODevice::ReadOnly); } else { d->archive = nullptr; } d->descriptionFileName = other.d->descriptionFileName; return *this; } void SourceFileTemplate::setTemplateDescription(const QString& templateDescription) { delete d->archive; d->descriptionFileName = templateDescription; QString archiveFileName; const QString templateBaseName = QFileInfo(templateDescription).baseName(); d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/kdevfiletemplates/templates/"), QStandardPaths::LocateDirectory)); foreach (const QString& dir, d->searchLocations) { foreach (const auto& entry, QDir(dir).entryInfoList(QDir::Files)) { if (entry.baseName() == templateBaseName) { archiveFileName = entry.absoluteFilePath(); qCDebug(LANGUAGE) << "Found template archive" << archiveFileName; break; } } } if (archiveFileName.isEmpty() || !QFileInfo::exists(archiveFileName)) { qCWarning(LANGUAGE) << "Could not find a template archive for description" << templateDescription << ", archive file" << archiveFileName; d->archive = nullptr; } else { QFileInfo info(archiveFileName); if (info.suffix() == QLatin1String("zip")) { d->archive = new KZip(archiveFileName); } else { d->archive = new KTar(archiveFileName); } d->archive->open(QIODevice::ReadOnly); } } bool SourceFileTemplate::isValid() const { return d->archive; } QString SourceFileTemplate::name() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Name"); } QString SourceFileTemplate::type() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Type", QString()); } QString SourceFileTemplate::languageName() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Language", QString()); } QStringList SourceFileTemplate::category() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("Category", QStringList()); } QStringList SourceFileTemplate::defaultBaseClasses() const { KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); return cg.readEntry("BaseClasses", QStringList()); } const KArchiveDirectory* SourceFileTemplate::directory() const { Q_ASSERT(isValid()); return d->archive->directory(); } QVector SourceFileTemplate::outputFiles() const { QVector outputFiles; KConfig templateConfig(d->descriptionFileName); KConfigGroup group(&templateConfig, "General"); const QStringList files = group.readEntry("Files", QStringList()); qCDebug(LANGUAGE) << "Files in template" << files; outputFiles.reserve(files.size()); for (const QString& fileGroup : files) { KConfigGroup cg(&templateConfig, fileGroup); OutputFile f; f.identifier = cg.name(); f.label = cg.readEntry("Name"); f.fileName = cg.readEntry("File"); f.outputName = cg.readEntry("OutputFile"); outputFiles << f; } return outputFiles; } bool SourceFileTemplate::hasCustomOptions() const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); bool hasOptions = d->archive->directory()->entries().contains(cg.readEntry("OptionsFile", "options.kcfg")); qCDebug(LANGUAGE) << cg.readEntry("OptionsFile", "options.kcfg") << hasOptions; return hasOptions; } QVector SourceFileTemplate::customOptions(TemplateRenderer* renderer) const { Q_ASSERT(isValid()); KConfig templateConfig(d->descriptionFileName); KConfigGroup cg(&templateConfig, "General"); const KArchiveEntry* entry = d->archive->directory()->entry(cg.readEntry("OptionsFile", "options.kcfg")); QVector optionGroups; if (!entry->isFile()) { return optionGroups; } const auto* file = static_cast(entry); /* * Copied from kconfig_compiler.kcfg */ QDomDocument doc; QString errorMsg; int errorRow; int errorCol; if (!doc.setContent(file->data(), &errorMsg, &errorRow, &errorCol)) { qCDebug(LANGUAGE) << "Unable to load document."; qCDebug(LANGUAGE) << "Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; return optionGroups; } QDomElement cfgElement = doc.documentElement(); if (cfgElement.isNull()) { qCDebug(LANGUAGE) << "No document in kcfg file"; return optionGroups; } QDomNodeList groups = cfgElement.elementsByTagName(QStringLiteral("group")); optionGroups.reserve(groups.size()); for (int i = 0; i < groups.size(); ++i) { QDomElement group = groups.at(i).toElement(); ConfigOptionGroup optionGroup; optionGroup.name = group.attribute(QStringLiteral("name")); QDomNodeList entries = group.elementsByTagName(QStringLiteral("entry")); optionGroup.options.reserve(entries.size()); for (int j = 0; j < entries.size(); ++j) { QDomElement entry = entries.at(j).toElement(); optionGroup.options << d->readEntry(entry, renderer); } optionGroups << optionGroup; } return optionGroups; } void SourceFileTemplate::addAdditionalSearchLocation(const QString& location) { if (!d->searchLocations.contains(location)) d->searchLocations.append(location); } diff --git a/kdevplatform/language/codegen/templateclassgenerator.h b/kdevplatform/language/codegen/templateclassgenerator.h index b19993164d..5580c7715c 100644 --- a/kdevplatform/language/codegen/templateclassgenerator.h +++ b/kdevplatform/language/codegen/templateclassgenerator.h @@ -1,178 +1,178 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 KDEVPLATFORM_TEMPLATECLASSGENERATOR_H #define KDEVPLATFORM_TEMPLATECLASSGENERATOR_H #include #include #include #include class QUrl; namespace KTextEditor { class Cursor; } namespace KDevelop { struct ClassDescription; class TemplateRenderer; class SourceFileTemplate; class DocumentChangeSet; /** * Generates new classes from templates * * * @section Variables Variables Passed to Templates * * TemplateClassGenerator makes use of the ClassDescription returned by ClassGenerator::description(). * From this description, it constructs the following variables: * @li @c description (ClassDescription) - the class description * @li @c name (QString) - the class name, same as @c description.name * @li @c namespaces (QStringList) - the list of nested namespaces in which the class will be declared * @li @c identifier (QString) - the full class identifier, composed of namespaces and name * @li @c members (VariableDescriptionList) - data members, same as @c description.members * @li @c functions (FunctionDescriptionList) - function members, same as @c description.methods * @li @c base_classes (InheritanceDescriptionList) - directly inherited classes, same as @c description.baseClasses * @li @c license (QString) - the license for this class, including author copyright, without comment characters or indentation. It is recommended to use the "lines_prepend" filters from library "kdev_filters" to format it. * * For each output file, TemplateRenderer add two variables named @c output_file_x * and @c output_file_x_absolute, where @c x is replaced * with the file name specified in the template description file. * See TemplateRenderer::renderFileTemplate() for details. * * If the templates uses custom options, these options are added to the template variables. Their names match the * names specified in the options file, and their values to the values entered by the user. * * Subclasses can override templateVariables() and insert additional variables. * **/ class KDEVPLATFORMLANGUAGE_EXPORT TemplateClassGenerator { public: - typedef QHash UrlHash; + using UrlHash = QHash; /** * Creates a new generator. * * You should call setTemplateDescription() before any other template-related functions. * * @param baseUrl the folder where new files will be created **/ explicit TemplateClassGenerator(const QUrl& baseUrl); virtual ~TemplateClassGenerator(); /** * @brief Selects the template to be used * * This function must be called before using any other functions. * * The passed @p templateDescription should be an absolute path to a template description (.desktop) file. * TemplateClassGenerator will attempt to find a template archive with a matching name. * * @param templateDescription the template description file **/ void setTemplateDescription(const SourceFileTemplate& templateDescription); /** * Set the name (without namespace) for this class */ void setName(const QString&); /** * \return The name of the class to generate (excluding namespaces) */ QString name() const; /** * \param identifier The Qualified identifier that the class will have */ virtual void setIdentifier(const QString& identifier); /** * \return The Identifier of the class to generate (including all used namespaces) */ virtual QString identifier() const; /** * \param namespaces The list of nested namespaces in which this class is to be declared */ virtual void setNamespaces(const QStringList& namespaces) const; /** * \return The list of nested namespace in which this class will be declared */ virtual QStringList namespaces() const; void addBaseClass(const QString& base); void setBaseClasses(const QList& bases); QList directBaseClasses() const; QList allBaseClasses() const; void setLicense(const QString& license); QString license() const; void setDescription(const ClassDescription& description); ClassDescription description() const; virtual DocumentChangeSet generate(); QHash fileLabels() const; QUrl baseUrl() const; UrlHash fileUrls() const; void setFileUrl(const QString& outputFile, const QUrl& url); QUrl fileUrl(const QString& outputFile) const; void setFilePosition(const QString& outputFile, const KTextEditor::Cursor& position); KTextEditor::Cursor filePosition(const QString& outputFile) const; SourceFileTemplate sourceFileTemplate() const; /** * Adds variables @p variables to the context passed to all template files. * * The variable values must be of a type registered with Grantlee::registerMetaType() * * @param variables additional variables to be passed to all templates **/ void addVariables(const QVariantHash& variables); /** * Convenience function to render a string @p text as a Grantlee template **/ QString renderString(const QString& text) const; /** * The template renderer used to render all the templates for this class. * * This function is useful if you want a rendeder with all current template variables. */ TemplateRenderer* renderer() const; private: const QScopedPointer d; }; } #endif // KDEVPLATFORM_TEMPLATECLASSGENERATOR_H diff --git a/kdevplatform/language/duchain/appendedlist.h b/kdevplatform/language/duchain/appendedlist.h index bf618a4422..e21fdea057 100644 --- a/kdevplatform/language/duchain/appendedlist.h +++ b/kdevplatform/language/duchain/appendedlist.h @@ -1,434 +1,434 @@ /* 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. */ #ifndef KDEVPLATFORM_APPENDEDLIST_H #define KDEVPLATFORM_APPENDEDLIST_H #include #include #include #include #include #include namespace KDevelop { class AbstractItemRepository; /** * This file contains macros and classes that can be used to conveniently implement classes that store the data of an arbitrary count * of additional lists within the same memory block directly behind the class data, in a way that one the whole data can be stored by one copy-operation * to another place, like needed in ItemRepository. These macros simplify having two versions of a class: One that has its lists attached in memory, * and one version that has them contained as a directly accessible KDevVarLengthArray. Both versions have their lists accessible through access-functions, * have a completeSize() function that computes the size of the one-block version, and a copyListsFrom(..) function which can copy the lists from one * version to the other. * * @warning Always follow these rules: * \li You must call initializeAppendedLists(bool) on construction, also in any copy-constructor, but before calling copyFrom(..). * \li The parameter to that function should be whether the lists in the items should be dynamic, and thus most times "true". * \li You must call freeAppendedLists() on destruction, our you will be leaking memory(only when dynamic) * * For each embedded list, you must use macros to define a global hash that will be used to allocate the temporary lists. * For example in @c identifier.cpp we have: * * @code * DEFINE_LIST_MEMBER_HASH(IdentifierPrivate, templateIdentifiers, uint); * @endcode * * In general, see @c identifier.cpp for an example on how to use these macros. * * @todo Document this a bit more * */ enum { DynamicAppendedListMask = 1 << 31 }; enum { DynamicAppendedListRevertMask = ~DynamicAppendedListMask }; /** * Manages a repository of items for temporary usage. The items will be allocated with an index on alloc(), * and freed on free(index). When freed, the same index will be re-used for a later allocation, thus no real allocations * will be happening in most cases. * The returned indices will always be ored with DynamicAppendedListMask. * */ template class TemporaryDataManager { public: explicit TemporaryDataManager(const QByteArray& id = {}) : m_id(id) { int first = alloc(); //Allocate the zero item, just to reserve that index Q_ASSERT(first == ( int )DynamicAppendedListMask); Q_UNUSED(first); } ~TemporaryDataManager() { free(DynamicAppendedListMask); //Free the zero index, so we don't get wrong warnings int cnt = usedItemCount(); if (cnt) //Don't use qDebug, because that may not work during destruction std::cout << m_id.constData() << " There were items left on destruction: " << usedItemCount() << "\n"; for (int a = 0; a < m_items.size(); ++a) delete m_items.at(a); } inline T& item(int index) { //For performance reasons this function does not lock the mutex, it's called too often and must be //extremely fast. There is special measures in alloc() to make this safe. Q_ASSERT(index & DynamicAppendedListMask); return *m_items.at(index & KDevelop::DynamicAppendedListRevertMask); } ///Allocates an item index, which from now on you can get using item(), until you call free(..) on the index. ///The returned item is not initialized and may contain random older content, so you should clear it after getting it for the first time int alloc() { if (threadSafe) m_mutex.lock(); int ret; if (!m_freeIndicesWithData.isEmpty()) { ret = m_freeIndicesWithData.pop(); } else if (!m_freeIndices.isEmpty()) { ret = m_freeIndices.pop(); Q_ASSERT(!m_items.at(ret)); m_items[ret] = new T; } else { if (m_items.size() >= m_items.capacity()) { //We need to re-allocate const int newItemsSize = m_items.capacity() + 20 + (m_items.capacity() / 3); const QVector oldItems = m_items; // backup m_items.reserve(newItemsSize); // detach, grow container const auto now = time(nullptr); // We do this in this place so it isn't called too often. The result is that we will always have some additional data around. // However the index itself should anyway not consume too much data. while (!m_deleteLater.isEmpty()) { // We delete only after 5 seconds if (now - m_deleteLater.first().first <= 5) { break; } m_deleteLater.removeFirst(); } //The only function that does not lock the mutex is item(..), because that function must be very efficient. //Since it's only a few instructions from the moment m_items is read to the moment it's used, //deleting the old data after a few seconds should be safe. m_deleteLater.append(qMakePair(now, oldItems)); } ret = m_items.size(); m_items.append(new T); Q_ASSERT(m_items.size() <= m_items.capacity()); } if (threadSafe) m_mutex.unlock(); Q_ASSERT(!(ret & DynamicAppendedListMask)); return ret | DynamicAppendedListMask; } void free(int index) { Q_ASSERT(index & DynamicAppendedListMask); index &= KDevelop::DynamicAppendedListRevertMask; if (threadSafe) m_mutex.lock(); freeItem(m_items.at(index)); m_freeIndicesWithData.push(index); //Hold the amount of free indices with data between 100 and 200 if (m_freeIndicesWithData.size() > 200) { for (int a = 0; a < 100; ++a) { int deleteIndexData = m_freeIndicesWithData.pop(); auto& item = m_items[deleteIndexData]; delete item; item = nullptr; m_freeIndices.push(deleteIndexData); } } if (threadSafe) m_mutex.unlock(); } int usedItemCount() const { int ret = 0; for (int a = 0; a < m_items.size(); ++a) if (m_items.at(a)) ++ret; return ret - m_freeIndicesWithData.size(); } private: //To save some memory, clear the lists void freeItem(T* item) { item->clear(); ///@todo make this a template specialization that only does this for containers } QVector m_items; /// note: non-shared, ref count of 1 when accessed with non-const methods => no detach Stack m_freeIndicesWithData; Stack m_freeIndices; QMutex m_mutex; QByteArray m_id; QList>> m_deleteLater; }; ///Foreach macro that takes a container and a function-name, and will iterate through the vector returned by that function, using the length returned by the function-name with "Size" appended. //This might be a little slow #define FOREACH_FUNCTION(item, container) \ for (uint a__ = 0, mustDo__ = 1, containerSize = container ## Size(); a__ < containerSize; ++a__) \ if ((mustDo__ == 0 || mustDo__ == 1) && (mustDo__ = 2)) \ for (item(container()[a__]); mustDo__; mustDo__ = 0) #define DEFINE_LIST_MEMBER_HASH(container, member, type) \ - typedef KDevelop::TemporaryDataManager> temporaryHash ## container ## member ## Type; \ + using temporaryHash ## container ## member ## Type = KDevelop::TemporaryDataManager>; \ Q_GLOBAL_STATIC_WITH_ARGS(temporaryHash ## container ## member ## Type, \ temporaryHash ## container ## member ## Static, ( # container "::" # member)) \ temporaryHash ## container ## member ## Type & temporaryHash ## container ## member() { \ return *temporaryHash ## container ## member ## Static; \ } #define DECLARE_LIST_MEMBER_HASH(container, member, type) \ KDevelop::TemporaryDataManager> &temporaryHash ## container ## member(); ///This implements the interfaces so this container can be used as a predecessor for classes with appended lists. ///You should do this within the abstract base class that opens a tree of classes that can have appended lists, ///so each class that uses them, can also give its predecessor to START_APPENDE_LISTS, to increase flexibility. ///This creates a boolean entry that is initialized when initializeAppendedLists is called. ///You can call appendedListsDynamic() to find out whether the item is marked as dynamic. ///When this item is used, the same rules have to be followed as for a class with appended lists: You have to call ///initializeAppendedLists(...) and freeAppendedLists(..) ///Also, when you use this, you have to implement a uint classSize() function, that returns the size of the class including derived classes, ///but not including the dynamic data. Optionally you can implement a static bool appendedListDynamicDefault() function, that returns the default-value for the "dynamic" parameter. ///to initializeAppendedLists. #define APPENDED_LISTS_STUB(container) \ bool m_dynamic : 1; \ unsigned int offsetBehindLastList() const { return 0; } \ uint dynamicSize() const { return classSize(); } \ template bool listsEqual(const T& /*rhs*/) const { return true; } \ template void copyAllFrom(const T& /*rhs*/) const { } \ void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { m_dynamic = dynamic; } \ void freeAppendedLists() { } \ bool appendedListsDynamic() const { return m_dynamic; } ///use this if the class does not have a base class that also uses appended lists #define START_APPENDED_LISTS(container) \ unsigned int offsetBehindBase() const { return 0; } \ void freeDynamicData() { freeAppendedLists(); } ///Use this if one of the base-classes of the container also has the appended lists interfaces implemented. ///To reduce the probability of future problems, you should give the direct base class this one inherits from. ///@note: Multiple inheritance is not supported, however it will work ok if only one of the base-classes uses appended lists. #define START_APPENDED_LISTS_BASE(container, base) \ unsigned int offsetBehindBase() const { return base :: offsetBehindLastList(); } \ void freeDynamicData() { freeAppendedLists(); base::freeDynamicData(); } #define APPENDED_LIST_COMMON(container, type, name) \ uint name ## Data; \ unsigned int name ## Size() const { if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ return 0; if (!appendedListsDynamic()) \ return name ## Data; else \ return temporaryHash ## container ## name().item(name ## Data).size(); } \ KDevVarLengthArray& name ## List() { name ## NeedDynamicList(); \ return temporaryHash ## container ## name().item(name ## Data); } \ template bool name ## Equals(const T &rhs) const { unsigned int size = name ## Size(); \ if (size != rhs.name ## Size()) \ return false; for (uint a = 0; a < size; \ ++a) {if (!(name()[a] == \ rhs.name()[a])) \ return \ false; \ } return true; } \ template void name ## CopyFrom(const T &rhs) { \ if (rhs.name ## Size() == 0 && (name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ return; \ if (appendedListsDynamic()) { \ name ## NeedDynamicList(); \ KDevVarLengthArray& item(temporaryHash ## container ## name().item(name ## Data)); \ item.clear(); \ const type* otherCurr = rhs.name(); \ const type* otherEnd = otherCurr + rhs.name ## Size(); \ for (; otherCurr < otherEnd; ++otherCurr) \ item.append(*otherCurr); \ } else { \ Q_ASSERT(name ## Data == 0); /* It is dangerous to overwrite the contents of non-dynamic lists(Most probably a mistake) */ \ name ## Data = rhs.name ## Size(); \ type* curr = const_cast(name()); type* end = curr + name ## Size(); \ const type* otherCurr = rhs.name(); \ for (; curr < end; ++curr, ++otherCurr) \ new (curr) type(*otherCurr); /* Call the copy constructors */ \ } \ } \ void name ## NeedDynamicList() { \ Q_ASSERT(appendedListsDynamic()); \ if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) { \ name ## Data = temporaryHash ## container ## name().alloc(); \ Q_ASSERT(temporaryHash ## container ## name().item(name ## Data).isEmpty()); \ } \ } \ void name ## Initialize(bool dynamic) { name ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); } \ void name ## Free() { \ if (appendedListsDynamic()) { \ if (name ## Data & KDevelop::DynamicAppendedListRevertMask) \ temporaryHash ## container ## name().free(name ## Data); \ } else { \ type* curr = const_cast(name()); \ type* end = curr + name ## Size(); \ for (; curr < end; ++curr) \ curr->~type(); /*call destructors*/ \ } \ } \ ///@todo Make these things a bit faster(less recursion) #define APPENDED_LIST_FIRST(container, type, name) \ APPENDED_LIST_COMMON(container, type, name) \ const type * name() const { \ if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ return nullptr; \ if (!appendedListsDynamic()) \ return reinterpret_cast(reinterpret_cast(this) + classSize() + \ offsetBehindBase()); \ else \ return temporaryHash ## container ## name().item(name ## Data).data(); \ } \ unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + offsetBehindBase(); } \ template bool name ## ListChainEquals(const T &rhs) const { return name ## Equals(rhs); } \ template void name ## CopyAllFrom(const T &rhs) { name ## CopyFrom(rhs); } \ void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); } \ void name ## FreeChain() { name ## Free(); } #define APPENDED_LIST(container, type, name, predecessor) \ APPENDED_LIST_COMMON(container, type, name) \ const type * name() const { \ if ((name ## Data & KDevelop::DynamicAppendedListRevertMask) == 0) \ return nullptr; \ if (!appendedListsDynamic()) \ return reinterpret_cast(reinterpret_cast(this) + classSize() + \ predecessor ## OffsetBehind()); \ else \ return temporaryHash ## container ## name().item(name ## Data).data(); \ } \ unsigned int name ## OffsetBehind() const { return name ## Size() * sizeof(type) + predecessor ## OffsetBehind(); } \ template bool name ## ListChainEquals(const T &rhs) const { return name ## Equals(rhs) && \ predecessor ## ListChainEquals(rhs); } \ template void name ## CopyAllFrom(const T &rhs) { predecessor ## CopyAllFrom(rhs); name ## CopyFrom(rhs); \ } \ void name ## InitializeChain(bool dynamic) { name ## Initialize(dynamic); predecessor ## InitializeChain(dynamic); \ } \ void name ## FreeChain() { name ## Free(); predecessor ## FreeChain(); } #define END_APPENDED_LISTS(container, predecessor) \ /* Returns the size of the object containing the appended lists, including them */ \ unsigned int completeSize() const { return classSize() + predecessor ## OffsetBehind(); } \ /* Compares all local appended lists(not from base classes) and returns true if they are equal */ \ template bool listsEqual(const T &rhs) const { return predecessor ## ListChainEquals(rhs); } \ /* Copies all the local appended lists(not from base classes) from the given item.*/ \ template void copyListsFrom(const T &rhs) { return predecessor ## CopyAllFrom(rhs); } \ void initializeAppendedLists(bool dynamic = appendedListDynamicDefault()) { \ predecessor ## Data = (dynamic ? KDevelop::DynamicAppendedListMask : 0); \ predecessor ## InitializeChain(dynamic); \ } \ void freeAppendedLists() { predecessor ## FreeChain(); } \ bool appendedListsDynamic() const { return predecessor ## Data & KDevelop::DynamicAppendedListMask; } \ unsigned int offsetBehindLastList() const { return predecessor ## OffsetBehind(); } \ uint dynamicSize() const { return offsetBehindLastList() + classSize(); } /** * This is a class that allows you easily putting instances of your class into an ItemRepository as seen in itemrepository.h. * All your class needs to do is: * - Be implemented using the APPENDED_LIST macros. * - Have a real copy-constructor that additionally takes a "bool dynamic = true" parameter, which should be given to initializeAppendedLists * - Except for these appended lists, only contain directly copyable data like indices(no pointers, no virtual functions) * - Implement operator==(..) which should compare everything, including the lists. @warning The default operator will not work! * - Implement a hash() function. The hash should equal for two instances when operator==(..) returns true. * - Should be completely functional without a constructor called, only the data copied * - Implement a "bool persistent() const" function, that should check the reference-count or other information to decide whether the item should stay in the repository * If those conditions are fulfilled, the data can easily be put into a repository using this request class. * */ template class AppendedListItemRequest { public: AppendedListItemRequest(const Type& item) : m_item(item) { } enum { AverageSize = sizeof(Type) + averageAppendedBytes }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.dynamicSize(); } void createItem(Type* item) const { new (item) Type(m_item, false); } static void destroy(Type* item, KDevelop::AbstractItemRepository&) { item->~Type(); } static bool persistent(const Type* item) { return item->persistent(); } bool equals(const Type* item) const { return m_item == *item; } const Type& m_item; }; } ///This function is outside of the namespace, so it can always be found. It's used as default-parameter to initializeAppendedLists(..), ///and you can for example implement a function called like this in your local class hierarchy to override this default. inline bool appendedListDynamicDefault() { return true; } #endif diff --git a/kdevplatform/language/duchain/appendedlist_static.h b/kdevplatform/language/duchain/appendedlist_static.h index b418ed6345..045077749b 100644 --- a/kdevplatform/language/duchain/appendedlist_static.h +++ b/kdevplatform/language/duchain/appendedlist_static.h @@ -1,167 +1,167 @@ /* 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. */ #ifndef KDEVPLATFORM_APPENDEDLIST_STATIC_H #define KDEVPLATFORM_APPENDEDLIST_STATIC_H #include #include namespace KDevelop { /** * This file contains macros and classes that can be used to conveniently * implement classes that store the data of an arbitrary count * of additional lists within the same memory block directly behind the * class data, in a way that one the whole data can be stored by one copy-operation * to another place, like needed in ItemRepository. These macros simplify * having two versions of a class: One that has its lists attached in memory, * and one version that has them contained as a directly accessible * KDevVarLengthArray. Both versions have their lists accessible through access-functions, * have a completeSize() function that computes the size of the one-block * version, and a copyListsFrom(..) function which can copy the lists from one * version to the other. The class that contains these lists must have * a boolean template parameter called "dynamic". * * See identifier.cpp for an example how to use these classes. @todo Document this a bit more * */ // Foreach macro that takes a container and a function-name, and will iterate through the vector returned by that function, using the length returned by the function-name with "Size" appended. //This might be a little slow #define FOREACH_FUNCTION_STATIC(item, container) \ for (uint a__ = 0, mustDo__ = 1; a__ < container ## Size(); ++a__) \ if ((mustDo__ == 0 || mustDo__ == 1) && (mustDo__ = 2)) \ for (item(container()[a__]); mustDo__; mustDo__ = 0) -#define START_APPENDED_LISTS_STATIC(selftype) typedef selftype SelfType; +#define START_APPENDED_LISTS_STATIC(selftype) using SelfType = selftype; #define APPENDED_LIST_COMMON_STATIC(type, name) \ KDevelop::AppendedList name ## List; \ unsigned int name ## Size() const { return name ## List.size(); } \ template bool name ## Equals(const T &rhs) const { \ unsigned int size = name ## Size(); \ if (size != rhs.name ## Size()) \ return false; \ for (uint a = 0; a < size; ++a) {if (!(name()[a] == rhs.name()[a])) \ return false; } \ return true; \ } ///@todo Make these things a bit faster(less recursion) #define APPENDED_LIST_FIRST_STATIC(type, name) \ APPENDED_LIST_COMMON_STATIC(type, name) \ const type * name() const { return name ## List.data(reinterpret_cast(this) + sizeof(SelfType)); } \ unsigned int name ## OffsetBehind() const { return name ## List.dynamicDataSize(); } \ template bool name ## ListChainEquals(const T &rhs) const { return name ## Equals(rhs); } \ template void name ## CopyAllFrom(const T &rhs) { name ## List.copy(const_cast(name()), \ rhs.name(), rhs.name ## Size()); } #define APPENDED_LIST_STATIC(type, name, predecessor) \ APPENDED_LIST_COMMON_STATIC(type, name) \ const type * name() const { return name ## List.data( \ reinterpret_cast(this) + sizeof(SelfType) + \ predecessor ## OffsetBehind()); } \ unsigned int name ## OffsetBehind() const { return name ## List.dynamicDataSize() + predecessor ## OffsetBehind(); } \ template bool name ## ListChainEquals(const T &rhs) const { return name ## Equals(rhs) && \ predecessor ## ListChainEquals(rhs); } \ template void name ## CopyAllFrom(const T &rhs) { name ## List.copy(const_cast(name()), \ rhs.name(), rhs.name ## Size()); \ predecessor ## CopyAllFrom(); } #define END_APPENDED_LISTS_STATIC(predecessor) \ /* Returns the size of the object containing the appended lists, including them */ \ unsigned int completeSize() const { return sizeof(SelfType) + predecessor ## OffsetBehind(); } \ unsigned int lastOffsetBehind() const { return predecessor ## OffsetBehind(); } \ /* Compares all appended lists and returns true if they are equal */ \ template bool listsEqual(const T &rhs) const { return predecessor ## ListChainEquals(rhs); } \ template void copyListsFrom(const T &rhs) { return predecessor ## CopyAllFrom(rhs); } template class AppendedList : public KDevVarLengthArray { public: unsigned int dynamicDataSize() const { return this->size() * sizeof(T); } const T* data(const char* /*position*/) const { return KDevVarLengthArray::data(); } void copy(T* /*target*/, const T* data, uint size) { Q_ASSERT(!shouldDoDUChainReferenceCounting(KDevVarLengthArray::data())); bool empty = KDevVarLengthArray::isEmpty(); Q_ASSERT(empty); Q_UNUSED(empty); for (uint a = 0; a < size; ++a) this->append(data[a]); } void free(T*) { Q_ASSERT(!shouldDoDUChainReferenceCounting(KDevVarLengthArray::data())); } }; template class AppendedList { public: AppendedList() { } unsigned int listSize = 0; unsigned int size() const { return listSize; } void free(T* position) { if (listSize) Q_ASSERT(shouldDoDUChainReferenceCounting(position)); //Since it's constant, it must be in the repository for (unsigned int a = 0; a < listSize; ++a) (position + a)->~T(); } //currentOffset should point to the position where the data of this item should be saved const T* data(const char* position) const { return reinterpret_cast(position); } //Count of bytes that were appended unsigned int dynamicDataSize() const { return listSize * sizeof(T); } void copy(T* target, const T* data, uint size) { if (size) Q_ASSERT(shouldDoDUChainReferenceCounting(target)); //Since it's constant, it must be in the repository for (uint a = 0; a < size; ++a) new (target + a) T(data[a]); //Properly call all the copy constructors listSize = size; } }; } #endif diff --git a/kdevplatform/language/duchain/classfunctiondeclaration.h b/kdevplatform/language/duchain/classfunctiondeclaration.h index f069f00c91..a2575666ce 100644 --- a/kdevplatform/language/duchain/classfunctiondeclaration.h +++ b/kdevplatform/language/duchain/classfunctiondeclaration.h @@ -1,131 +1,130 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006 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. */ #ifndef KDEVPLATFORM_CLASSFUNCTIONDECLARATION_H #define KDEVPLATFORM_CLASSFUNCTIONDECLARATION_H #include "classmemberdeclaration.h" #include "abstractfunctiondeclaration.h" #include "classmemberdeclarationdata.h" namespace KDevelop { enum ClassFunctionFlag { FunctionFlagNormal = 0, FunctionSignalFlag = 1 << 1, FunctionSlotFlag = 1 << 2, AbstractFunctionFlag = 1 << 3, FinalFunctionFlag = 1 << 4 }; Q_DECLARE_FLAGS(ClassFunctionFlags, ClassFunctionFlag) KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(ClassFunctionDeclarationData, m_defaultParameters, IndexedString) class KDEVPLATFORMLANGUAGE_EXPORT ClassFunctionDeclarationData : public ClassMemberDeclarationData , public AbstractFunctionDeclarationData { public: ClassFunctionDeclarationData() { initializeAppendedLists(); m_functionFlags = FunctionFlagNormal; } ClassFunctionDeclarationData(const ClassFunctionDeclarationData& rhs) : ClassMemberDeclarationData(rhs) , AbstractFunctionDeclarationData(rhs) { initializeAppendedLists(); copyListsFrom(rhs); m_functionFlags = rhs.m_functionFlags; } ~ClassFunctionDeclarationData() { freeAppendedLists(); } ClassFunctionFlags m_functionFlags; START_APPENDED_LISTS_BASE(ClassFunctionDeclarationData, ClassMemberDeclarationData); APPENDED_LIST_FIRST(ClassFunctionDeclarationData, IndexedString, m_defaultParameters); END_APPENDED_LISTS(ClassFunctionDeclarationData, m_defaultParameters); }; /** * Represents a single variable definition in a definition-use chain. */ -typedef MergeAbstractFunctionDeclaration ClassFunctionDeclarationBase; +using ClassFunctionDeclarationBase = MergeAbstractFunctionDeclaration; class KDEVPLATFORMLANGUAGE_EXPORT ClassFunctionDeclaration : public ClassFunctionDeclarationBase { public: ClassFunctionDeclaration(const RangeInRevision& range, DUContext* context); ClassFunctionDeclaration(ClassFunctionDeclarationData& data, const RangeInRevision& range, DUContext* context); explicit ClassFunctionDeclaration(ClassFunctionDeclarationData& data); ~ClassFunctionDeclaration() override; ///Whether this function is a signal, for example a C++ Qt signal bool isSignal() const; void setIsSignal(bool); ///Whether this function is a slot, for example a C++ Qt slot bool isSlot() const; void setIsSlot(bool); ///Whether this function is abstract bool isAbstract() const; void setIsAbstract(bool); ///Whether this function is final bool isFinal() const; void setIsFinal(bool); virtual bool isConstructor() const; virtual bool isDestructor() const; bool isConversionFunction() const; bool isFunctionDeclaration() const override; QString toString() const override; void setAbstractType(AbstractType::Ptr type) override; Declaration* clonePrivate() const override; uint additionalIdentity() const override; const IndexedString* defaultParameters() const override; unsigned int defaultParametersSize() const override; void addDefaultParameter(const IndexedString& str) override; void clearDefaultParameters() override; enum { Identity = 14 }; protected: ClassFunctionDeclaration(const ClassFunctionDeclaration& rhs); private: DUCHAIN_DECLARE_DATA(ClassFunctionDeclaration) }; } #endif // KDEVPLATFORM_CLASSFUNCTIONDECLARATION_H diff --git a/kdevplatform/language/duchain/duchain.h b/kdevplatform/language/duchain/duchain.h index 2411051e09..c2f68cb3e9 100644 --- a/kdevplatform/language/duchain/duchain.h +++ b/kdevplatform/language/duchain/duchain.h @@ -1,323 +1,323 @@ /* This file 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. */ #ifndef KDEVPLATFORM_DUCHAIN_H #define KDEVPLATFORM_DUCHAIN_H #include #include "topducontext.h" #include "parsingenvironment.h" #include class QUrl; namespace KDevelop { class IDocument; class TopDUContext; class DUChainLock; class ParsingEnvironmentManager; class ParsingEnvironment; class ParsingEnvironmentFile; -typedef QExplicitlySharedDataPointer ParsingEnvironmentFilePointer; +using ParsingEnvironmentFilePointer = QExplicitlySharedDataPointer; class Definitions; class Uses; /** * \short Holds references to all top level source file contexts. * * The DUChain is a global static class which manages the definition-use * chains. It performs the following functions: * \li registers chains with addDocumentChain() and deregisters with removeDocumentChain() * \li allows querying for existing chains * \li watches text editors, registering and deregistering them with the BackgroundParser when files * are opened and closed. */ class KDEVPLATFORMLANGUAGE_EXPORT DUChain : public QObject { Q_OBJECT public: /** * Initializes common static item repositories. * Must be called once for multi threaded applications to work reliably. */ static void initialize(); /** * Return a list of all chains available */ QList allChains() const; /** * Makes sure the standard-context for the given url is up-to-date. * This may trigger a parsing in background, so a QObject can be given that will be notified * asyonchronously once the update is ready. * If the context is already up to date, the given QObject is notified directly. * * @param document Document to update * @param minFeatures The requested features. If you want to force a full update of the context, give TopDUContext::ForceUpdate. * If you want to force an update including all imports, use TopDUContext::ForceUpdateRecursive. * @param notifyReady An optional pointer to a QObject that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once for each call to updateContextForUrl. The given top-context * may be invalid if the update failed. A queued connection is used if a re-parse has to be done. The duchain * will _not_ be locked when updateReady is called. * @param priority An optional priority for the job. The lower the value, the higher it's priority. * @note The duchain must _not_ be locked when this is called! */ void updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady = nullptr, int priority = 1) const; /** * Convenience-function similar to updateContextForUrl that blocks this thread until the update of the given document is ready, * and returns the top-context. * @param document The document to update * @param minFeatures The requested features. If you want to force a full update of the context, give TopDUContext::ForceUpdate. * If you want to force an update including all imports, use TopDUContext::ForceUpdateRecursive. * @return The up-to-date top-context, or zero if the update failed * * @note The duchain must _not_ be locked when this is called! * */ KDevelop::ReferencedTopDUContext waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext = false); /** * Return any chain for the given document * If available, the version accepting IndexedString should be used instead of this, for performance reasons. * When no fitting chain is in memory, one may be loaded from disk. * * @note The duchain must be at least read-locked locked when this is called! * */ TopDUContext* chainForDocument(const QUrl& document, bool proxyContext = false) const; TopDUContext* chainForDocument(const IndexedString& document, bool proxyContext = false) const; /** * Return all chains for the given document that are currently in memory. * This does not load any chains from disk. * */ QList chainsForDocument(const QUrl& document) const; /** * Return all chains for the given document that are currently in memory. * This does not load any chains from disk. * Should be preferred over the QUrl version. * */ QList chainsForDocument(const IndexedString& document) const; /** * Find a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * @note The duchain must be at least read-locked locked when this is called! * */ TopDUContext* chainForDocument(const QUrl& document, const ParsingEnvironment* environment, bool proxyContext = false) const; /** * Find a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * Prefer this over the QUrl version. * * @note The duchain must be at least read-locked locked when this is called! * */ TopDUContext* chainForDocument(const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext = false) const; /** * Find the environment-file of a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * * This should be preferred over chainForDocument when only the environment-info is needed, because the TopDUContext is not loaded in this function. * ** @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * Prefer this over the QUrl version. * * @note The duchain must be at least read-locked locked when this is called! * */ ParsingEnvironmentFilePointer environmentFileForDocument(const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext = false) const; ParsingEnvironmentFilePointer environmentFileForDocument(IndexedTopDUContext topContext) const; /** * Returns the list of the environment-infos of all versions of the given document. */ QList allEnvironmentFiles(const IndexedString& document); ///Returns the top-context that has the given index assigned, or zero if it doesn't exist. @see TopDUContext::ownIndex ///The duchain must be read-locked when this is called ///This function is inlined because it is called in a very high frequency inline TopDUContext* chainForIndex(uint index) { if (m_deleted) return nullptr; { QMutexLocker lock(&chainsByIndexLock); if (chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if (top) return top; } } //Load the top-context return loadChain(index); } ///Returns the url for the given top-context index if available. This does have some cost, so avoid it when possible. IndexedString urlForIndex(uint index) const; /// Only used for debugging at the moment QList documents() const; /// Only used for debugging at the moment /// Prefer that over the QUrl version for performance reasons QList indexedDocuments() const; /** * Registers a new definition-use \a chain for the given \a document. */ void addDocumentChain(TopDUContext* chain); /// Returns true if the global duchain instance has already been deleted static bool deleted(); /// Returns the global static instance. static DUChain* self(); /// Returns the structure that manages mapping between definitions and declarations static Definitions* definitions(); /// Returns the structure that manages mapping between declarations, and which top level contexts contain uses of them. static Uses* uses(); static QString repositoryPathForSession(const KDevelop::ISessionLock::Ptr& session); /** * Retrieve the read write lock for the entire definition-use chain. * To call non-const methods, you must be holding a write lock. * * Evaluations made prior to holding a lock (including which objects * exist) must be verified once the lock is held, as they may have changed * or been deleted. * * \threadsafe */ static DUChainLock* lock(); /// Returns whether the top-context with the given index is currently loaded in memory bool isInMemory(uint topContextIndex) const; /** * Changes the environment attached to the given top-level context, and updates the management-structures to reflect that * */ void updateContextEnvironment(TopDUContext* context, ParsingEnvironmentFile* file); ///Allocates a new identity for a new top-context, no lock needed. The returned value is never zero static uint newTopContextIndex(); ///If you call this, the persistent disk-storage structure will stay unaffected, and no duchain cleanup will be done. ///Call this from within tests. void disablePersistentStorage(bool disable = true); ///Stores the whole duchain and all its repositories in the current state to disk ///The duchain must not be locked in any way void storeToDisk(); ///Compares the whole duchain and all its repositories in the current state to disk ///When the comparison fails, debug-output will show why ///The duchain must not be locked when calling this ///@return true If the current memory state equals the disk state, else false bool compareToDisk(); Q_SIGNALS: ///Is emitted when the declaration has been selected somewhere in the user-interface, for example in the completion-list void declarationSelected(const KDevelop::DeclarationPointer& decl); /** * This signal is emitted whenever the DUChain data associated with @p url was updated. * * You can connect to this signal to get notified when the DUChain for a given file was updated. */ void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); public Q_SLOTS: ///Removes the given top-context from the duchain, and deletes it. void removeDocumentChain(KDevelop::TopDUContext* document); ///Emits the declarationSelected signal, so other parties can notice it. void emitDeclarationSelected(const KDevelop::DeclarationPointer& decl); /** * Call this after you have modified the DUChain data associated with the file @p url. * * This triggers an emit of the @c updateReady signal. */ void emitUpdateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); /** * Shutdown and cleanup the DUChain. */ void shutdown(); private Q_SLOTS: void documentActivated(KDevelop::IDocument* doc); void documentLoadedPrepare(KDevelop::IDocument* document); void documentRenamed(KDevelop::IDocument* document); void documentClosed(KDevelop::IDocument*); private: TopDUContext* loadChain(uint index); //These two are exported here so that the extremely frequently called chainForIndex(..) can be inlined static bool m_deleted; static std::vector chainsByIndex; static QMutex chainsByIndexLock; /// Increases the reference-count for the given top-context. The result: It will not be unloaded. /// Do this to prevent KDevelop from unloading a top-context that you plan to use. Don't forget calling unReferenceToContext again, /// else the top-context will stay in memory for ever. void refCountUp(TopDUContext* top); /// Decreases the reference-count for the given top-context. When it reaches zero, KDevelop is free to unload it at any time, /// also invalidating all the contained declarations and contexts. void refCountDown(TopDUContext* top); void addToEnvironmentManager(TopDUContext* chain); void removeFromEnvironmentManager(TopDUContext* chain); DUChain(); ~DUChain() override; friend class DUChainPrivate; friend class ReferencedTopDUContext; }; } #endif // KDEVPLATFORM_DUCHAIN_H diff --git a/kdevplatform/language/duchain/duchainbase.h b/kdevplatform/language/duchain/duchainbase.h index 8abec78563..37954090b9 100644 --- a/kdevplatform/language/duchain/duchainbase.h +++ b/kdevplatform/language/duchain/duchainbase.h @@ -1,228 +1,228 @@ /* This file is part of KDevelop Copyright 2006 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. */ #ifndef KDEVPLATFORM_DUCHAINBASE_H #define KDEVPLATFORM_DUCHAINBASE_H #include #include "appendedlist.h" #include "duchainpointer.h" #include #include namespace KTextEditor { class Cursor; class Range; } namespace KDevelop { class DUContext; class TopDUContext; class DUChainBase; class IndexedString; ///Use this to declare the data functions in your DUChainBase based class. @warning Behind this macro, the access will be "public". #define DUCHAIN_DECLARE_DATA(Class) \ inline class Class ## Data * d_func_dynamic() { makeDynamic(); return reinterpret_cast(d_ptr); } \ inline const class Class ## Data* d_func() const { return reinterpret_cast(d_ptr); } \ -public: typedef Class ## Data Data; \ +public: using Data = Class ## Data; \ private: #define DUCHAIN_D(Class) const Class ## Data * const d = d_func() #define DUCHAIN_D_DYNAMIC(Class) Class ## Data * const d = d_func_dynamic() ///@note When a data-item is stored on disk, no destructors of contained items will be called while destruction. ///DUChainBase assumes that each item that has constant data, is stored on disk. ///However the destructor is called even on constant items, when they have been replaced with a dynamic item. ///This tries to keep constructor/destructor count consistency persistently, which allows doing static reference-counting ///using contained classes in their constructor/destructors(For example the class Utils::StorableSet). ///This means that the data of all items that are stored to disk _MUST_ be made constant before their destruction. ///This also means that every item that is "semantically" deleted, _MUST_ have dynamic data before its destruction. ///This also means that DUChainBaseData based items should never be cloned using memcpy, but rather always using the copy-constructor, ///even if both sides are constant. class KDEVPLATFORMLANGUAGE_EXPORT DUChainBaseData { public: DUChainBaseData() { initializeAppendedLists(); } DUChainBaseData(const DUChainBaseData& rhs) : m_range(rhs.m_range) , classId(rhs.classId) { initializeAppendedLists(); } ~DUChainBaseData() { freeAppendedLists(); } RangeInRevision m_range; APPENDED_LISTS_STUB(DUChainBaseData) quint16 classId = 0; bool isDynamic() const { return m_dynamic; } /** * Internal setup for the data structure. * * This must be called from actual class that belongs to this data(not parent classes), and the class must have the * "Identity" enumerator with a unique identity. Do NOT call this in copy-constructors! */ template void setClassId(T*) { static_assert(T::Identity < std::numeric_limits::max(), "Class ID out of bounds"); classId = T::Identity; } uint classSize() const; ///This is called whenever the data-object is being deleted memory-wise, but not semantically(Which means it stays on disk) ///Implementations of parent-classes must always be called void freeDynamicData() { } ///Used to decide whether a constructed item should create constant data. ///The default is "false", so dynamic data is created by default. ///This is stored thread-locally. static bool& shouldCreateConstantData(); ///Returns whether initialized objects should be created as dynamic objects static bool appendedListDynamicDefault() { return !shouldCreateConstantData(); } }; /** * Base class for definition-use chain objects. * * This class provides a thread safe pointer type to reference duchain objects * while the DUChain mutex is not held (\see DUChainPointer) */ class KDEVPLATFORMLANGUAGE_EXPORT DUChainBase { public: /** * Constructor. * * \param range range of the alias declaration's identifier */ explicit DUChainBase(const RangeInRevision& range); /// Destructor virtual ~DUChainBase(); /** * Determine the top context to which this object belongs. */ virtual TopDUContext* topContext() const; /** * Returns a special pointer that can be used to track the existence of a du-chain object across locking-cycles. * @see DUChainPointerData * */ const QExplicitlySharedDataPointer& weakPointer() const; virtual IndexedString url() const; enum { Identity = 1 }; ///After this was called, the data-pointer is dynamic. It is cloned if needed. void makeDynamic(); explicit DUChainBase(DUChainBaseData& dd); ///This must only be used to change the storage-location or storage-kind(dynamic/constant) of the data, but ///the data must always be equal! virtual void setData(DUChainBaseData*, bool constructorCalled = true); ///Returns the range assigned to this object, in the document revision when this document was last parsed. RangeInRevision range() const; ///Changes the range assigned to this object, in the document revision when this document is parsed. void setRange(const RangeInRevision& range); ///Returns the range assigned to this object, transformed into the current revision of the document. ///@warning This must only be called from the foreground thread, or with the foreground lock acquired. KTextEditor::Range rangeInCurrentRevision() const; ///Returns the range assigned to this object, transformed into the current revision of the document. ///The returned object is unique at each call, so you can use it and change it in whatever way you want. ///@warning This must only be called from the foreground thread, or with the foreground lock acquired. PersistentMovingRange::Ptr createRangeMoving() const; ///Transforms the given cursor in the current document revision to its according position ///in the parsed document containing this duchain object. The resulting cursor will be directly comparable to the non-translated ///range() members in the duchain, but only for one duchain locking cycle. ///@warning This must only be called from the foreground thread, or with the foreground lock acquired. CursorInRevision transformToLocalRevision(const KTextEditor::Cursor& cursor) const; ///Transforms the given range in the current document revision to its according position ///in the parsed document containing this duchain object. The resulting cursor will be directly comparable to the non-translated ///range() members in the duchain, but only for one duchain locking cycle. ///@warning This must only be called from the foreground thread, or with the foreground lock acquired. RangeInRevision transformToLocalRevision(const KTextEditor::Range& range) const; KTextEditor::Cursor transformFromLocalRevision(const CursorInRevision& cursor) const; KTextEditor::Range transformFromLocalRevision(const RangeInRevision& range) const; protected: /** * Creates a duchain object that uses the data of the given one, and will not delete it on destruction. * The data will be shared, and this object must be deleted before the given one is. */ DUChainBase(DUChainBase& rhs); /** * Constructor for copy constructors in subclasses. * * \param dd data to use. * \param range text range which this object covers. */ DUChainBase(DUChainBaseData& dd, const RangeInRevision& range); ///Called after loading to rebuild the dynamic data. If this is a context, this should recursively work on all sub-contexts. virtual void rebuildDynamicData(DUContext* parent, uint ownIndex); /// Data pointer that is shared across all the inheritance hierarchy DUChainBaseData* d_ptr; private: mutable QExplicitlySharedDataPointer m_ptr; public: DUCHAIN_DECLARE_DATA(DUChainBase) }; } #endif // KDEVPLATFORM_DUCHAINBASE_H diff --git a/kdevplatform/language/duchain/duchainpointer.h b/kdevplatform/language/duchain/duchainpointer.h index fccfc6dbaf..b990b3f27a 100644 --- a/kdevplatform/language/duchain/duchainpointer.h +++ b/kdevplatform/language/duchain/duchainpointer.h @@ -1,207 +1,207 @@ /* Copyright 2007 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 KDEVPLATFORM_DUCHAINPOINTER_H #define KDEVPLATFORM_DUCHAINPOINTER_H #include #include #include #include //krazy:excludeall=dpointer namespace KDevelop { class DUContext; class TopDUContext; class DUChainBase; class Declaration; class AbstractFunctionDeclaration; /** * Whenever the du-chain is unlocked and locked again, any du-chain item may have been deleted in between. * For that reason, the following class should be used to make sure that no deleted objects are accessed. It contains a pointer * that will be reset to zero once the pointed object is deleted. * * Access to the data must still be serialized through duchain-locking. Using this comes with no additional cost. * * In practice this means: * Store an instance of DUChainPointer instead of a pointer to the du-chain object. * Then, access the eventually still existing object by calling pointer->base(). * * To make it even more convenient see DUChainPointer * */ class KDEVPLATFORMLANGUAGE_EXPORT DUChainPointerData : public QSharedData { public: /** * Will return zero once the pointed-to object was deleted * */ DUChainBase* base(); /** * Will return zero once the pointed-to object was deleted * */ DUChainBase* base() const; ///Default-initialization of an invalid reference DUChainPointerData(); ~DUChainPointerData(); private: ///Should not be used from outside, but is needed sometimes to construct an invalid dummy-pointer explicit DUChainPointerData(DUChainBase* base); friend class DUChainBase; DUChainBase* m_base = nullptr; Q_DISABLE_COPY(DUChainPointerData) }; /** * A smart-pointer similar class that conveniently wraps around DUChainPointerData without * too many dynamic casts. * * It can be used like a normal pointer. In order to cast between pointer types, you should * use the staticCast() and dynamicCast() functions as appropriate. * * Access must be serialized by holding the KDevelop::DUChain::lock() as appropriate for the * function(s) being called. **/ template class DUChainPointer { template friend class DUChainPointer; public: DUChainPointer() : d(QExplicitlySharedDataPointer(nullptr)) { } DUChainPointer(const DUChainPointer& rhs) : d(rhs.d) { } ///This constructor includes dynamic casting. If the object cannot be casted to the type, the constructed DUChainPointer will have value zero. template explicit DUChainPointer(OtherType* rhs) { if (dynamic_cast(rhs)) d = rhs->weakPointer(); } template explicit DUChainPointer(DUChainPointer rhs) { if (dynamic_cast(rhs.data())) d = rhs.d; } explicit DUChainPointer(QExplicitlySharedDataPointer rhs) { if (dynamic_cast(rhs->base())) d = rhs; } explicit DUChainPointer(Type* rhs) { if (rhs) d = rhs->weakPointer(); } bool operator ==(const DUChainPointer& rhs) const { return d.data() == rhs.d.data(); } bool operator !=(const DUChainPointer& rhs) const { return d.data() != rhs.d.data(); } ///Returns whether the pointed object is still existing operator bool() const { return d && d->base(); } Type& operator*() const { Q_ASSERT(d); return *static_cast(d->base()); } Type* operator->() const { Q_ASSERT(d); return static_cast(d->base()); } bool operator<(const DUChainPointer& rhs) const { return d.data() < rhs.d.data(); } template DUChainPointer dynamicCast() const { if (d && dynamic_cast(d->base())) //When the reference to the pointer is constant that doesn't mean that the pointed object needs to be constant return DUChainPointer(static_cast(d->base())); else return DUChainPointer(); } Type* data() const { if (!d) return nullptr; return static_cast(d->base()); } DUChainPointer& operator=(Type* rhs) { if (rhs) d = rhs->weakPointer(); else d = nullptr; return *this; } private: QExplicitlySharedDataPointer d; }; -typedef DUChainPointer DUChainBasePointer; -typedef DUChainPointer DUContextPointer; -typedef DUChainPointer TopDUContextPointer; -typedef DUChainPointer DeclarationPointer; -typedef DUChainPointer FunctionDeclarationPointer; +using DUChainBasePointer = DUChainPointer; +using DUContextPointer = DUChainPointer; +using TopDUContextPointer = DUChainPointer; +using DeclarationPointer = DUChainPointer; +using FunctionDeclarationPointer = DUChainPointer; } Q_DECLARE_METATYPE(KDevelop::DUChainBasePointer) Q_DECLARE_METATYPE(KDevelop::DeclarationPointer) Q_DECLARE_METATYPE(KDevelop::DUContextPointer) Q_DECLARE_METATYPE(KDevelop::TopDUContextPointer) Q_DECLARE_METATYPE(QList ) #endif diff --git a/kdevplatform/language/duchain/ducontext.h b/kdevplatform/language/duchain/ducontext.h index abfd3ac4c0..a7a6461623 100644 --- a/kdevplatform/language/duchain/ducontext.h +++ b/kdevplatform/language/duchain/ducontext.h @@ -1,967 +1,967 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-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 KDEVPLATFORM_DUCONTEXT_H #define KDEVPLATFORM_DUCONTEXT_H #include #include #include #include #include #include "identifier.h" #include "duchainbase.h" #include "types/abstracttype.h" #include "duchainpointer.h" #include "declarationid.h" #include "indexedducontext.h" #include "navigation/abstractnavigationwidget.h" class QWidget; namespace KDevelop { class Declaration; class DUChain; class Use; class TopDUContext; class DUContext; class DUContextData; class KDEVPLATFORMLANGUAGE_EXPORT DUChainVisitor { public: virtual void visit(DUContext* context) = 0; virtual void visit(Declaration* declaration) = 0; virtual ~DUChainVisitor(); }; -typedef DUChainPointer DUContextPointer; +using DUContextPointer = DUChainPointer; /** * A single context in source code, represented as a node in a * directed acyclic graph. * * Access to context objects must be serialised by holding the * chain lock, ie. DUChain::lock(). * * NOTE: A du-context can be freely edited as long as it's parent-context is zero. * In the moment the parent-context is set, the context may only be edited when it * is allowed to edited it's top-level context(@see TopLevelContext::inDUChain() * * @todo change child relationships to a linked list within the context? */ class KDEVPLATFORMLANGUAGE_EXPORT DUContext : public DUChainBase { friend class Use; friend class Declaration; friend class DeclarationData; friend class DUContextData; friend class DUContextDynamicData; friend class Definition; friend class VisibleDeclarationIterator; public: /** * Constructor. No convenience methods, as the initialisation order is important, * * @param anonymous Whether the context should be added as an anonymous context to the parent. That way the context can never be found through any of the parent's member-functions. * * If the parent is in the symbol table and the context is not anonymous, it will also be added to the symbol table. You nead a write-lock to the DUChain then */ explicit DUContext(const RangeInRevision& range, DUContext* parent = nullptr, bool anonymous = false); explicit DUContext(DUContextData&); /** * Destructor. Will delete all child contexts which are defined within * the same file as this context. */ ~DUContext() override; enum ContextType : quint8 { Global /**< A context that declares functions, namespaces or classes */, Namespace /**< A context that declares namespace members */, Class /**< A context that declares class members */, Function /**< A context that declares function-arguments */, Template /**< A context that declares template-parameters */, Enum /**< A context that contains a list of enumerators */, Helper /**< A helper context. This context is treated specially during search: * when searching within the imports of a context, and that context's parent * is a context of type DUContext::Helper, then the upwards search is continued * into that helper(The decision happens in shouldSearchInParent) */, Other /**< Represents executable code, like for example within a compound-statement */ }; enum SearchFlag { NoSearchFlags = 0 /**< Searching for everything */, InImportedParentContext = 1 /**< Internal, do not use from outside */, OnlyContainerTypes = 2 /**< Not implemented yet */, DontSearchInParent = 4 /**< IF this flag is set, findDeclarations(..) will not search for the identifier in parent-contexts(which does not include imported parent-contexts) */, NoUndefinedTemplateParams = 8 /**< For languages that support templates(like C++). If this is set, the search should fail as soon as undefined template-parameters are involved. */, DirectQualifiedLookup = 16 /**< When this flag is used, the searched qualified identifier should NOT be split up into it's components and looked up one by one. Currently only plays a role in C++ specific parts. */, NoFiltering = 32 /**< Should be set when no filtering at all is wished, not even filtering that is natural for the underlying language(For example in C++, constructors are filtered out be default) */, OnlyFunctions = 64 /**< When this is given, only function-declarations are returned. In case of C++, this also means that constructors can be retrieved, while normally they are filtered out. */, NoImportsCheck = 128 /**< With this parameter, a global search will return all matching items, from all contexts, not only from imported ones. */, NoSelfLookUp = 256 /**< With this parameter, the special-treatment during search that allows finding the context-class by its name is disabled. */, DontResolveAliases = 512 /**< Disables the resolution of alias declarations in the returned list*/, LastSearchFlag = 1024 }; Q_DECLARE_FLAGS(SearchFlags, SearchFlag) ContextType type() const; void setType(ContextType type); /** * If this context was opened by a declaration or definition, this returns that item. * * The returned declaration/definition will have this context set as @c internalContext() */ Declaration* owner() const; /** * Sets the declaration/definition, and also updates it's internal context (they are strictly paired together). * * The declaration has to be part of the same top-context. */ void setOwner(Declaration* decl); /** * Calculate the depth of this context, from the top level context in the file. */ int depth() const; /** * Find the top context. */ TopDUContext* topContext() const override; /** * Visits all duchain objects in the whole duchain. * * Classes that hold a unique link to duchain objects like instantiations * have to pass the visitor over to those classes. * */ virtual void visit(DUChainVisitor& visitor); /** * Find the context which most specifically covers @p position. * * The search is recursive, so the most specific context is found. * * @param includeBorders When this is true, contexts will also be found that * have the position on their borders. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ DUContext* findContextAt(const CursorInRevision& position, bool includeBorders = false) const; /** * Find a child declaration that has a rang that covers the given @p position. * * The search is local, not recursive. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ Declaration* findDeclarationAt(const CursorInRevision& position) const; /** * Find the context which most specifically covers @a range. * * @warning This uses the ranges in the local revision of the document (at last parsing time). * Use DUChainBase::transformToLocalRevision to transform the cursor into that revision first. */ DUContext* findContextIncluding(const RangeInRevision& range) const; /** * Calculate the fully qualified scope identifier. */ QualifiedIdentifier scopeIdentifier(bool includeClasses = false) const; /** * Returns true if this context has the same scope identifier as the given one. * * @note This is much more efficient than computing the identifiers through @c scopeIdentifier(..) * and comparing them */ bool equalScopeIdentifier(const DUContext* rhs) const; /** * Scope identifier, used to qualify the identifiers occurring in each context. * * This is the part relative to the parent context. */ QualifiedIdentifier localScopeIdentifier() const; /** * Same as @c localScopeIdentifier(), but faster. */ IndexedQualifiedIdentifier indexedLocalScopeIdentifier() const; /** * Scope identifier, used to qualify the identifiers occurring in each context * This must not be called once this context has children. */ void setLocalScopeIdentifier(const QualifiedIdentifier& identifier); /** * Returns whether this context is listed in the symbol table (Namespaces and classes) */ bool inSymbolTable() const; /** * Move this object into/out of the symbol table. * * @note You need to have a duchain write lock, unless this is a TopDUContext. */ void setInSymbolTable(bool inSymbolTable); /** * Returns the immediate parent context of this context. */ DUContext* parentContext() const; /** * Represents an imported parent context. */ struct KDEVPLATFORMLANGUAGE_EXPORT Import { /** * @note DUChain must be read-locked when this is called */ Import(DUContext* context, const DUContext* importer, const CursorInRevision& position = CursorInRevision::invalid()); Import() : position(CursorInRevision::invalid()) { } explicit Import(const DeclarationId& id, const CursorInRevision& position = CursorInRevision::invalid()); bool operator==(const Import& rhs) const { return m_context == rhs.m_context && m_declaration == rhs.m_declaration; } /** * @param topContext The top-context from where to start searching. * This is important to find the correct imports * in the case of templates or similar structures. */ DUContext* context(const TopDUContext* topContext, bool instantiateIfRequired = true) const; /** * Returns the top-context index, if this import is not a specialization import. */ uint topContextIndex() const { return m_context.topContextIndex(); } IndexedDUContext indexedContext() const { return m_context; } /** * Returns true if this import is direct. * * That is, the import is not referred to by its identifier, * but rather directly by its index. */ bool isDirect() const; /** * If this import is indirect, returns the imported declaration-id */ DeclarationId indirectDeclarationId() const { return m_declaration; } CursorInRevision position; private: //Either we store m_declaration, or m_context. That way we can resolve specialized contexts. ///@todo Compress using union DeclarationId m_declaration; IndexedDUContext m_context; }; /** * Returns the list of imported parent contexts for this context. * * @warning The list may contain objects that are not valid any more, * i.e. data() returns zero, @see addImportedParentContext) * @warning The import structure may contain loops if this is a TopDUContext, * so be careful when traversing the tree. * @note This is expensive. */ virtual QVector importedParentContexts() const; /** * If the given context is directly imported into this one, and * @c addImportedParentContext(..) was called with a valid cursor, * this will return that position. Otherwise an invalid cursor is returned. */ virtual CursorInRevision importPosition(const DUContext* target) const; /** * Returns true if this context imports @param origin at any depth, else false. */ virtual bool imports(const DUContext* origin, const CursorInRevision& position = CursorInRevision::invalid()) const; /** * Adds an imported context. * * @param anonymous If this is true, the import will not be registered at the imported context. * This allows du-chain contexts importing without having a write-lock. * @param position Position where the context is imported. This is mainly important in C++ with included files. * * If the context is already imported, only the position is updated. * * @note Be sure to have set the text location first, so that the chain is sorted correctly. */ virtual void addImportedParentContext(DUContext* context, const CursorInRevision& position = CursorInRevision::invalid(), bool anonymous = false, bool temporary = false); /** * Adds an imported context, which may be indirect. * * @warning This is only allowed if this context is _NOT_ a top-context. * @warning When using this mechanism, this context will not be registered as importer to the other one. * @warning The given import _must_ be indirect. * * @return true if the import was already imported before, else false. */ bool addIndirectImport(const DUContext::Import& import); /** * Removes a child context. */ virtual void removeImportedParentContext(DUContext* context); /** * Clear all imported parent contexts. */ virtual void clearImportedParentContexts(); /** * If this is set to true, all declarations that are added to this context * will also be visible in the parent-context. * * They will be visible in the parent using @c findDeclarations(...) and * @c findLocalDeclarations(...), but will not be in the list of @c localDeclarations(...). */ void setPropagateDeclarations(bool propagate); bool isPropagateDeclarations() const; /** * Returns the list of contexts importing this context. * * @note Very expensive, since the importers top-contexts need to be loaded. */ virtual QVector importers() const; /** * Returns the list of indexed importers. * * Cheap, because nothing needs to be loaded. */ KDevVarLengthArray indexedImporters() const; /** * Returns the list of immediate child contexts for this context. * * @note This is expensive. */ QVector childContexts() const; /** * Clears and deletes all child contexts recursively. * * This will not cross file boundaries. */ void deleteChildContextsRecursively(); /** * Resort the child contexts by their range. * * You must call this when you manually change the range of child contexts in a way * that could break the internal range sorting. */ void resortChildContexts(); /** * Returns true if this declaration is accessible through the du-chain, * and thus cannot be edited without a du-chain write lock */ virtual bool inDUChain() const; /** * Retrieve the context which is specialized with the given * @a specialization as seen from the given @a topContext. * * @param specialization the specialization index (see DeclarationId) * @param topContext the top context representing the perspective from which to specialize. * if @p topContext is zero, only already existing specializations are returned, * and if none exists, zero is returned. * @param upDistance upwards distance in the context-structure of the * given specialization-info. This allows specializing children. */ virtual DUContext* specialize(const IndexedInstantiationInformation& specialization, const TopDUContext* topContext, int upDistance = 0); /** * Searches for and returns a declaration with a given @p identifier in this context, which * is currently active at the given text @p position, with the given type @p dataType. * In fact, only items are returned that are declared BEFORE that position. * * @param identifier the identifier of the definition to search for * @param position the text position to search for * @param topContext the top-context from where a completion is triggered. * This is needed so delayed types (templates in C++) can be resolved in the correct context. * @param dataType the type to match, or null for no type matching. * * @returns the requested declaration if one was found, otherwise null. * * @warning this may return declarations which are not in this tree, you may need to lock them too... */ QList findDeclarations(const QualifiedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const AbstractType::Ptr& dataType = AbstractType::Ptr(), const TopDUContext* topContext = nullptr, SearchFlags flags = NoSearchFlags) const; /** * Searches for and returns a declaration with a given @a identifier in this context, which * is currently active at the given text @a position. * * @param identifier the identifier of the definition to search for * @param topContext the top-context from where a completion is triggered. * This is needed so delayed types(templates in C++) can be resolved in the correct context. * @param position the text position to search for * * @returns the requested declaration if one was found, otherwise null. * * @warning this may return declarations which are not in this tree, you may need to lock them too... * * @overload */ QList findDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, SearchFlags flags = NoSearchFlags) const; /** * Prefer the version above for speed reasons. */ QList findDeclarations(const Identifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, SearchFlags flags = NoSearchFlags) const; /** * Returns the type of any @a identifier defined in this context, or * null if one is not found. * * Does not search imported parent-contexts(like base-classes). */ QList findLocalDeclarations(const IndexedIdentifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, const AbstractType::Ptr& dataType = AbstractType::Ptr(), SearchFlags flags = NoSearchFlags) const; /** * Prefer the version above for speed reasons. */ QList findLocalDeclarations(const Identifier& identifier, const CursorInRevision& position = CursorInRevision::invalid(), const TopDUContext* topContext = nullptr, const AbstractType::Ptr& dataType = AbstractType::Ptr(), SearchFlags flags = NoSearchFlags) const; /** * Clears all local declarations. * * Does not delete the declaration; the caller assumes ownership. */ QVector clearLocalDeclarations(); /** * Clears all local declarations. * * Deletes these declarations, as the context has ownership. */ void deleteLocalDeclarations(); /** * Returns all local declarations * * @param source A source-context that is needed to instantiate template-declarations in some cases. * If it is zero, that signalizes that missing members should not be instantiated. */ virtual QVector localDeclarations(const TopDUContext* source = nullptr) const; /** * Resort the local declarations by their range. * * You must call this when you manually change the range of declarations in a way * that could break the internal range sorting. */ void resortLocalDeclarations(); /** * Searches for the most specific context for the given cursor @p position in the given @p url. * * @param position the text position to search for * @param parent the parent context to search from (this is mostly an internal detail, but if you only * want to search in a subbranch of the chain, you may specify the parent here) * * @returns the requested context if one was found, otherwise null. */ DUContext* findContext(const CursorInRevision& position, DUContext* parent = nullptr) const; /** * Iterates the tree to see if the provided @a context is a subcontext of this context. * * @returns true if @a context is a subcontext, otherwise false. */ bool parentContextOf(DUContext* context) const; /** * Return a list of all reachable declarations for a given cursor @p position in a given @p url. * * @param position the text position to search for * @param topContext the top-context from where a completion is triggered. * This is needed so delayed types(templates in C++) can be resolved * in the correct context. * @param searchInParents should declarations from parent-contexts be listed? * If false, only declarations from this and imported contexts will be returned. * * The returned declarations are paired together with their inheritance-depth, * which is the count of steps to into other contexts that were needed to find the declaration. * Declarations reached through a namespace- or global-context are offsetted by 1000. * * This also includes Declarations from sub-contexts that were propagated upwards * using @c setPropagateDeclarations(true). * * @returns the requested declarations, if any were active at that location. * Declarations propagated into this context(@c setPropagateDeclarations) are included. */ QVector> allDeclarations(const CursorInRevision& position, const TopDUContext* topContext, bool searchInParents = true) const; /** * Delete and remove all slaves (uses, declarations, definitions, contexts) that are not in the given set. */ void cleanIfNotEncountered(const QSet& encountered); /** * Uses: * A "Use" represents any position in a document where a Declaration is used literally. * For efficiency, since there can be many many uses, they are managed efficiently by * TopDUContext and DUContext. In TopDUContext, the used declarations are registered * and assigned a "Declaration-Index" while calling TopDUContext::indexForUsedDeclaration. * From such a declaration-index, the declaration can be retrieved back by calling * @c TopDUContext::usedDeclarationForIndex. * * The actual uses are stored within DUContext, where each use consists of a range and * the declaration-index of the used declaration. * */ /** * Return a vector of all uses which occur in this context. * * To get the actual declarations, use @c TopDUContext::usedDeclarationForIndex(..) * with the declarationIndex. */ const Use* uses() const; /** * Returns the count of uses that can be accessed through @c uses() */ int usesCount() const; /** * Determines whether the given declaration has uses or not */ static bool declarationHasUses(Declaration* decl); /** * Find the use which encompasses @a position, if one exists. * @return The local index of the use, or -1 */ int findUseAt(const CursorInRevision& position) const; /** * @note The change must not break the ordering */ void changeUseRange(int useIndex, const RangeInRevision& range); /** * Assigns the declaration represented by @p declarationIndex * to the use with index @p useIndex. */ void setUseDeclaration(int useIndex, int declarationIndex); /** * Creates a new use of the declaration given through @p declarationIndex. * The index must be retrieved through @c TopDUContext::indexForUsedDeclaration(..). * * @param range The range of the use * @param insertBefore A hint where in the vector of uses to insert the use. * Must be correct so the order is preserved(ordered by position), * or -1 to automatically choose the position. * * @return Local index of the created use */ int createUse(int declarationIndex, const RangeInRevision& range, int insertBefore = -1); /** * Deletes the use number @p index. * * @param index is the position in the vector of uses, not a used declaration index. */ void deleteUse(int index); /** * Clear and delete all uses in this context. */ virtual void deleteUses(); /** * Recursively delete all uses in this context and all its child-contexts */ virtual void deleteUsesRecursively(); /** * Can be specialized by languages to create a navigation/information-widget. * * Ideally, the widget would be based on @c KDevelop::QuickOpenEmbeddedWidgetInterface * for user-interaction within the quickopen list. * * The returned widget will be owned by the caller. * * @param decl A member-declaration of this context the navigation-widget should be created for. * Zero to create a widget for this context. * @param topContext Top-context from where the navigation-widget is triggered. * In C++, this is needed to resolve forward-declarations. * @param htmlPrefix Html-formatted text that should be prepended before any information shown by this widget * @param htmlSuffix Html-formatted text that should be appended to any information shown by this widget * * Can return zero which disables the navigation widget. * * If you setProperty("DoNotCloseOnCursorMove", true) on the widget returned, * then the widget will not close when the cursor moves in the document, which * enables you to change the document contents from the widget without immediately closing the widget. */ virtual QWidget* createNavigationWidget(Declaration* decl = nullptr, TopDUContext* topContext = nullptr, const QString& htmlPrefix = QString(), const QString& htmlSuffix = QString(), AbstractNavigationWidget::DisplayHints hints = AbstractNavigationWidget::NoHints) const; enum { Identity = 2 }; /** * Represents multiple qualified identifiers in a way that is better * to manipulate and allows applying namespace-aliases or -imports easily. * * A SearchItem generally represents a tree of identifiers, and represents * all the qualified identifiers that can be constructed by walking * along the tree starting at an arbitrary root-node into the depth using the "next" pointers. * * The insertion order in the hierarchy determines the order of the represented list. */ struct KDEVPLATFORMLANGUAGE_EXPORT SearchItem : public QSharedData { - typedef QExplicitlySharedDataPointer Ptr; - typedef KDevVarLengthArray PtrList; + using Ptr = QExplicitlySharedDataPointer; + using PtrList = KDevVarLengthArray; /** * Constructs a representation of the given @p id qualified identifier, * starting at its index @p start. * * @param nextItem is set as next item to the last item in the chain */ explicit SearchItem(const QualifiedIdentifier& id, const Ptr& nextItem = Ptr(), int start = 0); /** * Constructs a representation of the given @p id qualified identifier, * starting at its index @p start. * * @param nextItems is set as next item to the last item in the chain */ SearchItem(const QualifiedIdentifier& id, const PtrList& nextItems, int start = 0); SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const PtrList& nextItems); SearchItem(bool explicitlyGlobal, const IndexedIdentifier& id, const Ptr& nextItem); bool isEmpty() const; bool hasNext() const; /** * Appends the given item to every item that can be reached from this item, * and not only to the end items. * * The effect to search is that the given item is searched with all prefixes * contained in this earch-item prepended. * * @warning This changes all contained sub-nodes, but they can be shared with * other SearchItem trees. You should not use this on SearchItem trees * that have shared nodes with other trees. * * @note These functions ignore explicitly global items. */ void addToEachNode(const Ptr& item); void addToEachNode(const PtrList& items); /** * Returns true if the given identifier matches one of the identifiers * represented by this SearchItem. Does not respect the explicitlyGlobal flag */ bool match(const QualifiedIdentifier& id, int offset = 0) const; /** * @note expensive */ QVector toList(const QualifiedIdentifier& prefix = QualifiedIdentifier()) const; void addNext(const Ptr& other); bool isExplicitlyGlobal; IndexedIdentifier identifier; PtrList next; }; ///@todo Should be protected, moved here temporarily until I have figured ///out why the gcc 4.1.3 fails in cppducontext.h:212, which should work (within kdevelop) /// Declaration search implementation - typedef QList DeclarationList; + using DeclarationList = QList; /** * This is a more complex interface to the declaration search engine. * * Always prefer @c findDeclarations(..) when possible. * * Advantage of this interface: * - You can search multiple identifiers at one time. * However, those should be aliased identifiers for one single item, because * search might stop as soon as one item is found. * - The source top-context is needed to correctly resolve template-parameters * * @param position A valid position, if in doubt use textRange().end() * * @warning @p position must be valid! * * @param depth Depth of the search in parents. This is used to prevent endless * recursions in endless import loops. * * * @return whether the search was successful. If it is false, it had to be stopped * for special reasons (like some flags) */ virtual bool findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth) const; /** * Returns the qualified identifier @p id with all aliases (for example namespace imports) applied * * @example: If the namespace 'Foo' is imported, and id is 'Bar', * then the returned list is 'Bar' and 'Foo::Bar' */ QVector fullyApplyAliases(const QualifiedIdentifier& id, const TopDUContext* source) const; protected: /** * After one scope was searched, this function is asked whether more * results should be collected. Override it, for example to collect overloaded functions. * * The default-implementation returns true as soon as @p decls is not empty. */ virtual bool foundEnough(const DeclarationList& decls, SearchFlags flags) const; /** * Merges definitions and their inheritance-depth up all branches of the * definition-use chain into one hash. * * This includes declarations propagated from sub-contexts. * * @param hadContexts is used to count together all contexts that already were * visited, so they are not visited again. */ virtual void mergeDeclarationsInternal(QVector>& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents = true, int currentDepth = 0) const; void findLocalDeclarationsInternal(const Identifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags) const; virtual void findLocalDeclarationsInternal(const IndexedIdentifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags) const; /** * Applies namespace-imports and namespace-aliases and returns * possible absolute identifiers that need to be searched. * * @param targetIdentifiers will be filled with all identifiers that should * be searched for, instead of identifier. * @param onlyImports if this is true, namespace-aliases will not be respected, * but only imports. This is faster. */ void applyAliases(const SearchItem::PtrList& identifiers, SearchItem::PtrList& targetIdentifiers, const CursorInRevision& position, bool canBeNamespace, bool onlyImports = false) const; /** * Applies the aliases that need to be applied when moving the search * from this context up to the parent-context. * * The default-implementation adds a set of identifiers with the own local * identifier prefixed, if this is a namespace. * * For C++, this is needed when searching out of a namespace, so the item * can be found within that namespace in another place. */ virtual void applyUpwardsAliases(SearchItem::PtrList& identifiers, const TopDUContext* source) const; DUContext(DUContextData& dd, const RangeInRevision& range, DUContext* parent = nullptr, bool anonymous = false); /** * Just uses the data from the given context. Doesn't copy or change anything, * and the data will not be deleted on this contexts destruction. */ DUContext(DUContext& useDataFrom); /** * Whether this context, or any of its parent contexts, has been inserte * anonymously into the du-chain * * @see DUContext::DUContext */ bool isAnonymous() const; /** * This is called whenever the search needs to do the decision whether it * should be continued in the parent context. * * It is not called when the DontSearchInParent flag is set. Else this should * be overridden to do language-specific logic. * * The default implementation returns false if the flag InImportedParentContext is set. */ virtual bool shouldSearchInParent(SearchFlags flags) const; private: void rebuildDynamicData(DUContext* parent, uint ownIndex) override; friend class TopDUContext; friend class IndexedDUContext; friend class LocalIndexedDUContext; friend class TopDUContextDynamicData; DUCHAIN_DECLARE_DATA(DUContext) class DUContextDynamicData* m_dynamicData; }; /** * This is the identifier that can be used to search namespace-import declarations, * and should be used to store namespace-imports. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const Identifier& globalImportIdentifier(); /** * This is the identifier that can be used to search namespace-alias declarations. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const Identifier& globalAliasIdentifier(); /** * This is the identifier that can be used to search namespace-import declarations, * and should be used to store namespace-imports. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const IndexedIdentifier& globalIndexedImportIdentifier(); /** * This is the identifier that can be used to search namespace-alias declarations. * * It is stored statically for performance-reasons, so it doesn't need to be * constructed every time it is used. * * @see NamespaceAliasDeclaration. */ KDEVPLATFORMLANGUAGE_EXPORT const IndexedIdentifier& globalIndexedAliasIdentifier(); /** * Collects all uses of the given @p declarationIndex */ KDEVPLATFORMLANGUAGE_EXPORT QVector allUses(DUContext* context, int declarationIndex, bool noEmptyRanges = false); } Q_DECLARE_TYPEINFO(KDevelop::DUContext::Import, Q_MOVABLE_TYPE); KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug dbg, const KDevelop::DUContext::Import& import); #endif // KDEVPLATFORM_DUCONTEXT_H diff --git a/kdevplatform/language/duchain/ducontextdata.h b/kdevplatform/language/duchain/ducontextdata.h index fdb67d633b..cb201d7dfd 100644 --- a/kdevplatform/language/duchain/ducontextdata.h +++ b/kdevplatform/language/duchain/ducontextdata.h @@ -1,86 +1,86 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2006 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 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 DUCONTEXTDATA_H #define DUCONTEXTDATA_H #include "duchainbase.h" #include "ducontext.h" #include "duchainpointer.h" #include "declaration.h" #include "use.h" #include #include "localindexeddeclaration.h" #include "localindexedducontext.h" namespace KDevelop { class DUContext; KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(DUContextData, m_childContexts, LocalIndexedDUContext) KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(DUContextData, m_importers, IndexedDUContext) KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(DUContextData, m_importedContexts, DUContext::Import) KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(DUContextData, m_localDeclarations, LocalIndexedDeclaration) KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(DUContextData, m_uses, Use) ///This class contains data that needs to be stored to disk class KDEVPLATFORMLANGUAGE_EXPORT DUContextData : public DUChainBaseData { public: DUContextData(); ~DUContextData(); DUContextData(const DUContextData& rhs); IndexedQualifiedIdentifier m_scopeIdentifier; IndexedDeclaration m_owner; - typedef DUContext::Import Import; + using Import = DUContext::Import; START_APPENDED_LISTS_BASE(DUContextData, DUChainBaseData); APPENDED_LIST_FIRST(DUContextData, Import, m_importedContexts); APPENDED_LIST(DUContextData, LocalIndexedDUContext, m_childContexts, m_importedContexts); ///@todo Create an additional structure for importing to/from "temporary" contexts and classes in a way that it persists while saving/loading, /// and doesn't require changing a top-contexts data only because a class was derived from. APPENDED_LIST(DUContextData, IndexedDUContext, m_importers, m_childContexts); ///@warning: Whenever m_localDeclarations is read or written, the duchain must be locked APPENDED_LIST(DUContextData, LocalIndexedDeclaration, m_localDeclarations, m_importers); /** * Vector of all uses in this context * Mutable for range synchronization * */ APPENDED_LIST(DUContextData, Use, m_uses, m_localDeclarations); END_APPENDED_LISTS(DUContextData, m_uses); DUContext::ContextType m_contextType; bool m_inSymbolTable : 1; bool m_anonymousInParent : 1; //Whether this context was added anonymously into the parent. This means that it cannot be found as child-context in the parent. bool m_propagateDeclarations : 1; private: DUContextData& operator=(const DUContextData&) { return *this; } }; } #endif diff --git a/kdevplatform/language/duchain/forwarddeclaration.h b/kdevplatform/language/duchain/forwarddeclaration.h index 47cf25e37b..4830cf6988 100644 --- a/kdevplatform/language/duchain/forwarddeclaration.h +++ b/kdevplatform/language/duchain/forwarddeclaration.h @@ -1,92 +1,92 @@ /* This file 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. */ #ifndef KDEVPLATFORM_FORWARDDECLARATION_H #define KDEVPLATFORM_FORWARDDECLARATION_H #include "declaration.h" #include "declarationdata.h" namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ForwardDeclarationData : public DeclarationData { public: ForwardDeclarationData() { } ForwardDeclarationData(const ForwardDeclarationData& rhs) : DeclarationData(rhs) { } }; /** * Represents a forward declaration */ class KDEVPLATFORMLANGUAGE_EXPORT ForwardDeclaration : public Declaration { public: /** * Constructor. * * If \a context is in the symbol table, the declaration will automatically be added into the symbol table. * * \param range range of the alias declaration's identifier * \param context context in which this declaration occurred * */ ForwardDeclaration(const RangeInRevision& range, DUContext* context); explicit ForwardDeclaration(ForwardDeclarationData& data); ///Copy-constructor for cloning ForwardDeclaration(const ForwardDeclaration& rhs); /// Destructor. ~ForwardDeclaration() override; bool isForwardDeclaration() const override; /** * Resolved the forward-declaration using the given import-trace. * The topcontext is needed for correct functionality, and may only be * zero when the declaration is resolved starting from the top-context * the forward-declaration is contained in. * * If this forward-declaration has a type assigned that is not derived from ForwardDeclarationType, * and that is derived from IdentifiedType, the declaration of that type is returned here. * */ virtual Declaration* resolve(const TopDUContext* topContext) const; DUContext* logicalInternalContext(const TopDUContext* topContext) const override; QString toString() const override; enum { Identity = 10 }; - typedef Declaration BaseClass; + using BaseClass = Declaration; private: Declaration* clonePrivate() const override; DUCHAIN_DECLARE_DATA(ForwardDeclaration) }; } #endif // KDEVPLATFORM_DECLARATION_H diff --git a/kdevplatform/language/duchain/functiondeclaration.h b/kdevplatform/language/duchain/functiondeclaration.h index e03958b380..eadef469f2 100644 --- a/kdevplatform/language/duchain/functiondeclaration.h +++ b/kdevplatform/language/duchain/functiondeclaration.h @@ -1,96 +1,96 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat Copyright 2006-2007 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. */ #ifndef KDEVPLATFORM_FUNCTIONDECLARATION_H #define KDEVPLATFORM_FUNCTIONDECLARATION_H #include "declaration.h" #include "abstractfunctiondeclaration.h" #include "declarationdata.h" namespace KDevelop { KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(FunctionDeclarationData, m_defaultParameters, IndexedString) class KDEVPLATFORMLANGUAGE_EXPORT FunctionDeclarationData : public DeclarationData , public AbstractFunctionDeclarationData { public: FunctionDeclarationData() { initializeAppendedLists(); } FunctionDeclarationData(const FunctionDeclarationData& rhs) : DeclarationData(rhs) , AbstractFunctionDeclarationData(rhs) { initializeAppendedLists(); copyListsFrom(rhs); } ~FunctionDeclarationData() { freeAppendedLists(); } START_APPENDED_LISTS_BASE(FunctionDeclarationData, DeclarationData); APPENDED_LIST_FIRST(FunctionDeclarationData, IndexedString, m_defaultParameters); END_APPENDED_LISTS(FunctionDeclarationData, m_defaultParameters); }; /** * Represents a single variable definition in a definition-use chain. */ -typedef MergeAbstractFunctionDeclaration FunctionDeclarationBase; +using FunctionDeclarationBase = MergeAbstractFunctionDeclaration; class KDEVPLATFORMLANGUAGE_EXPORT FunctionDeclaration : public FunctionDeclarationBase { public: FunctionDeclaration(const FunctionDeclaration& rhs); FunctionDeclaration(const RangeInRevision& range, DUContext* context); explicit FunctionDeclaration(FunctionDeclarationData& data); FunctionDeclaration(FunctionDeclarationData& data, const KDevelop::RangeInRevision&); ~FunctionDeclaration() override; void setAbstractType(AbstractType::Ptr type) override; QString toString() const override; bool isFunctionDeclaration() const override; uint additionalIdentity() const override; const IndexedString* defaultParameters() const override; unsigned int defaultParametersSize() const override; void addDefaultParameter(const IndexedString& str) override; void clearDefaultParameters() override; enum { Identity = 12 }; - typedef Declaration Base; + using Base = Declaration; private: Declaration* clonePrivate() const override; DUCHAIN_DECLARE_DATA(FunctionDeclaration) }; } #endif // KDEVPLATFORM_FUNCTIONDECLARATION_H diff --git a/kdevplatform/language/duchain/identifier.cpp b/kdevplatform/language/duchain/identifier.cpp index 56e652b127..ecd60a227a 100644 --- a/kdevplatform/language/duchain/identifier.cpp +++ b/kdevplatform/language/duchain/identifier.cpp @@ -1,1593 +1,1593 @@ /* This file is part of KDevelop Copyright 2006 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 "identifier.h" #include #include "stringhelpers.h" #include "appendedlist_static.h" #include "serialization/itemrepository.h" #include "util/kdevhash.h" #include #include #include #define ifDebug(x) namespace KDevelop { template class IdentifierPrivate { public: IdentifierPrivate() { } template explicit IdentifierPrivate(const IdentifierPrivate& rhs) : m_unique(rhs.m_unique) , m_identifier(rhs.m_identifier) , m_refCount(0) , m_hash(rhs.m_hash) { copyListsFrom(rhs); } ~IdentifierPrivate() { templateIdentifiersList.free(const_cast(templateIdentifiers())); } //Flags the stored hash-value invalid void clearHash() { //This is always called on an object private to an Identifier, so there is no threading-problem. Q_ASSERT(dynamic); m_hash = 0; } uint hash() const { // Since this only needs reading and the data needs not to be private, this may be called by // multiple threads simultaneously, so computeHash() must be thread-safe. if (!m_hash && dynamic) computeHash(); return m_hash; } int m_unique = 0; IndexedString m_identifier; uint m_refCount = 0; START_APPENDED_LISTS_STATIC(IdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedTypeIdentifier, templateIdentifiers) END_APPENDED_LISTS_STATIC(templateIdentifiers) uint itemSize() const { return sizeof(IdentifierPrivate ) + lastOffsetBehind(); } void computeHash() const { Q_ASSERT(dynamic); //this must stay thread-safe(may be called by multiple threads at a time) //The thread-safety is given because all threads will have the same result, and it will only be written once at the end. KDevHash kdevhash; kdevhash << m_identifier.hash() << m_unique; FOREACH_FUNCTION_STATIC(const IndexedTypeIdentifier &templateIdentifier, templateIdentifiers) kdevhash << templateIdentifier.hash(); m_hash = kdevhash; } mutable uint m_hash = 0; }; -typedef IdentifierPrivate DynamicIdentifierPrivate; -typedef IdentifierPrivate ConstantIdentifierPrivate; +using DynamicIdentifierPrivate = IdentifierPrivate; +using ConstantIdentifierPrivate = IdentifierPrivate; struct IdentifierItemRequest { IdentifierItemRequest(const DynamicIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(IdentifierPrivate ) + 4 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(ConstantIdentifierPrivate* item) const { new (item) ConstantIdentifierPrivate(m_identifier); } static bool persistent(const ConstantIdentifierPrivate* item) { return ( bool )item->m_refCount; } static void destroy(ConstantIdentifierPrivate* item, AbstractItemRepository&) { item->~ConstantIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantIdentifierPrivate* item) const { return item->m_hash == m_identifier.m_hash && item->m_unique == m_identifier.m_unique && item->m_identifier == m_identifier.m_identifier && m_identifier.listsEqual(*item); } const DynamicIdentifierPrivate& m_identifier; }; using IdentifierRepository = RepositoryManager, false>; static IdentifierRepository& identifierRepository() { static IdentifierRepository identifierRepositoryObject(QStringLiteral("Identifier Repository")); return identifierRepositoryObject; } static uint emptyConstantIdentifierPrivateIndex() { static const uint index = identifierRepository()->index(DynamicIdentifierPrivate()); return index; } static const ConstantIdentifierPrivate* emptyConstantIdentifierPrivate() { static const ConstantIdentifierPrivate item; return &item; } bool IndexedIdentifier::isEmpty() const { return m_index == emptyConstantIdentifierPrivateIndex(); } /** * Before something is modified in QualifiedIdentifierPrivate, it must be made sure that * it is private to the QualifiedIdentifier it is used in(@see QualifiedIdentifier::prepareWrite) */ template class QualifiedIdentifierPrivate { public: QualifiedIdentifierPrivate() : m_explicitlyGlobal(false) , m_isExpression(false) { } template explicit QualifiedIdentifierPrivate(const QualifiedIdentifierPrivate& rhs) : m_explicitlyGlobal(rhs.m_explicitlyGlobal) , m_isExpression(rhs.m_isExpression) , m_hash(rhs.m_hash) , m_refCount(0) { copyListsFrom(rhs); } ~QualifiedIdentifierPrivate() { identifiersList.free(const_cast(identifiers())); } bool m_explicitlyGlobal : 1; bool m_isExpression : 1; mutable uint m_hash = 0; uint m_refCount = 0; START_APPENDED_LISTS_STATIC(QualifiedIdentifierPrivate) APPENDED_LIST_FIRST_STATIC(IndexedIdentifier, identifiers) END_APPENDED_LISTS_STATIC(identifiers) uint itemSize() const { return sizeof(QualifiedIdentifierPrivate ) + lastOffsetBehind(); } //Constructs m_identifiers void splitIdentifiers(const QString& str, int start) { Q_ASSERT(dynamic); uint currentStart = start; while (currentStart < ( uint )str.length()) { identifiersList.append(IndexedIdentifier(Identifier(str, currentStart, ¤tStart))); while (currentStart < ( uint )str.length() && (str[currentStart] == QLatin1Char(' '))) ++currentStart; currentStart += 2; //Skip "::" } } inline void clearHash() const { m_hash = 0; } uint hash() const { if (m_hash == 0) { KDevHash hash; quint32 bitfields = static_cast(m_explicitlyGlobal) | (m_isExpression << 1); hash << bitfields << identifiersSize(); FOREACH_FUNCTION_STATIC(const IndexedIdentifier &identifier, identifiers) { hash << identifier.index(); } m_hash = hash; } return m_hash; } }; -typedef QualifiedIdentifierPrivate DynamicQualifiedIdentifierPrivate; -typedef QualifiedIdentifierPrivate ConstantQualifiedIdentifierPrivate; +using DynamicQualifiedIdentifierPrivate = QualifiedIdentifierPrivate; +using ConstantQualifiedIdentifierPrivate = QualifiedIdentifierPrivate; struct QualifiedIdentifierItemRequest { QualifiedIdentifierItemRequest(const DynamicQualifiedIdentifierPrivate& identifier) : m_identifier(identifier) { identifier.hash(); //Make sure the hash is valid by calling this } enum { AverageSize = sizeof(QualifiedIdentifierPrivate ) + 8 }; //Should return the hash-value associated with this request(For example the hash of a string) uint hash() const { return m_identifier.hash(); } //Should return the size of an item created with createItem uint itemSize() const { return m_identifier.itemSize(); } /** * Should create an item where the information of the requested item is permanently stored. The pointer * @param item equals an allocated range with the size of itemSize(). */ void createItem(ConstantQualifiedIdentifierPrivate* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting((( char* )item) + (itemSize() - 1))); new (item) ConstantQualifiedIdentifierPrivate(m_identifier); } static bool persistent(const ConstantQualifiedIdentifierPrivate* item) { return ( bool )item->m_refCount; } static void destroy(ConstantQualifiedIdentifierPrivate* item, AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); item->~ConstantQualifiedIdentifierPrivate(); } //Should return whether the here requested item equals the given item bool equals(const ConstantQualifiedIdentifierPrivate* item) const { return item->m_explicitlyGlobal == m_identifier.m_explicitlyGlobal && item->m_isExpression == m_identifier.m_isExpression && item->m_hash == m_identifier.m_hash && m_identifier.listsEqual(*item); } const DynamicQualifiedIdentifierPrivate& m_identifier; }; using QualifiedIdentifierRepository = RepositoryManager, false>; static QualifiedIdentifierRepository& qualifiedidentifierRepository() { static QualifiedIdentifierRepository repo(QStringLiteral("Qualified Identifier Repository"), 1, []() -> AbstractRepositoryManager* { return &identifierRepository(); }); return repo; } static uint emptyConstantQualifiedIdentifierPrivateIndex() { static const uint index = qualifiedidentifierRepository()->index(DynamicQualifiedIdentifierPrivate()); return index; } static const ConstantQualifiedIdentifierPrivate* emptyConstantQualifiedIdentifierPrivate() { static const ConstantQualifiedIdentifierPrivate item; return &item; } Identifier::Identifier(const Identifier& rhs) { rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; } Identifier::Identifier(uint index) : m_index(index) { Q_ASSERT(m_index); cd = identifierRepository()->itemFromIndex(index); } Identifier::Identifier(const IndexedString& str) { if (str.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); } else { m_index = 0; dd = new IdentifierPrivate; dd->m_identifier = str; } } Identifier::Identifier(const QString& id, uint start, uint* takenRange) { if (id.isEmpty()) { m_index = emptyConstantIdentifierPrivateIndex(); cd = emptyConstantIdentifierPrivate(); return; } m_index = 0; dd = new IdentifierPrivate; ///Extract template-parameters ParamIterator paramIt(QStringLiteral("<>:"), id, start); dd->m_identifier = IndexedString(paramIt.prefix().trimmed()); while (paramIt) { appendTemplateIdentifier(IndexedTypeIdentifier(IndexedQualifiedIdentifier(QualifiedIdentifier(*paramIt)))); ++paramIt; } if (takenRange) *takenRange = paramIt.position(); } Identifier::Identifier() : m_index(emptyConstantIdentifierPrivateIndex()) , cd(emptyConstantIdentifierPrivate()) { } Identifier& Identifier::operator=(const Identifier& rhs) { if (dd == rhs.dd && cd == rhs.cd) return *this; if (!m_index) delete dd; dd = nullptr; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; Q_ASSERT(cd); return *this; } Identifier::Identifier(Identifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); } Identifier& Identifier::operator=(Identifier&& rhs) Q_DECL_NOEXCEPT { if (dd == rhs.dd && cd == rhs.cd) return *this; if (!m_index) { delete dd; dd = nullptr; } m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantIdentifierPrivate(); rhs.m_index = emptyConstantIdentifierPrivateIndex(); return *this; } Identifier::~Identifier() { if (!m_index) delete dd; } bool Identifier::nameEquals(const Identifier& rhs) const { return identifier() == rhs.identifier(); } uint Identifier::hash() const { if (!m_index) return dd->hash(); else return cd->hash(); } bool Identifier::isEmpty() const { if (!m_index) return dd->m_identifier.isEmpty() && dd->m_unique == 0 && dd->templateIdentifiersSize() == 0; else return cd->m_identifier.isEmpty() && cd->m_unique == 0 && cd->templateIdentifiersSize() == 0; } Identifier Identifier::unique(int token) { Identifier ret; ret.setUnique(token); return ret; } bool Identifier::isUnique() const { if (!m_index) return dd->m_unique; else return cd->m_unique; } int Identifier::uniqueToken() const { if (!m_index) return dd->m_unique; else return cd->m_unique; } void Identifier::setUnique(int token) { if (token != uniqueToken()) { prepareWrite(); dd->m_unique = token; } } const IndexedString Identifier::identifier() const { if (!m_index) return dd->m_identifier; else return cd->m_identifier; } void Identifier::setIdentifier(const QString& identifier) { IndexedString id(identifier); if (id != this->identifier()) { prepareWrite(); dd->m_identifier = std::move(id); } } void Identifier::setIdentifier(const IndexedString& identifier) { if (identifier != this->identifier()) { prepareWrite(); dd->m_identifier = identifier; } } IndexedTypeIdentifier Identifier::templateIdentifier(int num) const { if (!m_index) return dd->templateIdentifiers()[num]; else return cd->templateIdentifiers()[num]; } uint Identifier::templateIdentifiersCount() const { if (!m_index) return dd->templateIdentifiersSize(); else return cd->templateIdentifiersSize(); } void Identifier::appendTemplateIdentifier(const IndexedTypeIdentifier& identifier) { prepareWrite(); dd->templateIdentifiersList.append(identifier); } void Identifier::clearTemplateIdentifiers() { prepareWrite(); dd->templateIdentifiersList.clear(); } uint Identifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } bool Identifier::inRepository() const { return m_index; } void Identifier::setTemplateIdentifiers(const QList& templateIdentifiers) { prepareWrite(); dd->templateIdentifiersList.clear(); for (const IndexedTypeIdentifier& id : templateIdentifiers) { dd->templateIdentifiersList.append(id); } } QString Identifier::toString(IdentifierStringFormattingOptions options) const { QString ret = identifier().str(); if (!options.testFlag(RemoveTemplateInformation) && templateIdentifiersCount()) { QStringList templateIds; templateIds.reserve(templateIdentifiersCount()); for (uint i = 0; i < templateIdentifiersCount(); ++i) { templateIds.append(templateIdentifier(i).toString(options)); } ret += QStringLiteral("< ") + templateIds.join(QStringLiteral(", ")) + QStringLiteral(" >"); } return ret; } bool Identifier::operator==(const Identifier& rhs) const { return index() == rhs.index(); } bool Identifier::operator!=(const Identifier& rhs) const { return !operator==(rhs); } uint QualifiedIdentifier::index() const { makeConstant(); Q_ASSERT(m_index); return m_index; } void Identifier::makeConstant() const { if (m_index) return; m_index = identifierRepository()->index(IdentifierItemRequest(*dd)); delete dd; cd = identifierRepository()->itemFromIndex(m_index); } void Identifier::prepareWrite() { if (m_index) { const IdentifierPrivate* oldCc = cd; dd = new IdentifierPrivate; dd->m_hash = oldCc->m_hash; dd->m_unique = oldCc->m_unique; dd->m_identifier = oldCc->m_identifier; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } bool QualifiedIdentifier::inRepository() const { if (m_index) return true; else return ( bool )qualifiedidentifierRepository()->findIndex(QualifiedIdentifierItemRequest(*dd)); } QualifiedIdentifier::QualifiedIdentifier(uint index) : m_index(index) , cd(qualifiedidentifierRepository()->itemFromIndex(index)) { } QualifiedIdentifier::QualifiedIdentifier(const QString& id, bool isExpression) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if (isExpression) { setIsExpression(true); if (!id.isEmpty()) { //Prevent tokenization, since we may lose information there Identifier finishedId; finishedId.setIdentifier(id); push(finishedId); } } else { if (id.startsWith(QStringLiteral("::"))) { dd->m_explicitlyGlobal = true; dd->splitIdentifiers(id, 2); } else { dd->m_explicitlyGlobal = false; dd->splitIdentifiers(id, 0); } } } QualifiedIdentifier::QualifiedIdentifier(const Identifier& id) { if (id.isEmpty()) { m_index = emptyConstantQualifiedIdentifierPrivateIndex(); cd = emptyConstantQualifiedIdentifierPrivate(); return; } m_index = 0; dd = new DynamicQualifiedIdentifierPrivate; if (id.dd->m_identifier.str().isEmpty()) { dd->m_explicitlyGlobal = true; } else { dd->m_explicitlyGlobal = false; dd->identifiersList.append(IndexedIdentifier(id)); } } QualifiedIdentifier::QualifiedIdentifier() : m_index(emptyConstantQualifiedIdentifierPrivateIndex()) , cd(emptyConstantQualifiedIdentifierPrivate()) { } QualifiedIdentifier::QualifiedIdentifier(const QualifiedIdentifier& id) { if (id.m_index) { m_index = id.m_index; cd = id.cd; } else { m_index = 0; dd = new QualifiedIdentifierPrivate(*id.dd); } } QualifiedIdentifier::QualifiedIdentifier(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); rhs.cd = emptyConstantQualifiedIdentifierPrivate(); } QualifiedIdentifier& QualifiedIdentifier::operator=(const QualifiedIdentifier& rhs) { if (dd == rhs.dd && cd == rhs.cd) return *this; if (!m_index) delete dd; rhs.makeConstant(); cd = rhs.cd; m_index = rhs.m_index; return *this; } QualifiedIdentifier& QualifiedIdentifier::operator=(QualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if (!m_index) delete dd; m_index = rhs.m_index; if (m_index) { cd = rhs.cd; } else { dd = rhs.dd; } rhs.cd = emptyConstantQualifiedIdentifierPrivate(); rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); return *this; } QualifiedIdentifier::~QualifiedIdentifier() { if (!m_index) delete dd; } QStringList QualifiedIdentifier::toStringList(IdentifierStringFormattingOptions options) const { QStringList ret; ret.reserve(explicitlyGlobal() + count()); if (explicitlyGlobal()) ret.append(QString()); if (m_index) { ret.reserve(ret.size() + cd->identifiersSize()); FOREACH_FUNCTION_STATIC(const IndexedIdentifier &index, cd->identifiers) ret << index.identifier().toString(options); } else { ret.reserve(ret.size() + dd->identifiersSize()); FOREACH_FUNCTION_STATIC(const IndexedIdentifier &index, dd->identifiers) ret << index.identifier().toString(options); } return ret; } QString QualifiedIdentifier::toString(IdentifierStringFormattingOptions options) const { const QString doubleColon = QStringLiteral("::"); QString ret; if (!options.testFlag(RemoveExplicitlyGlobalPrefix) && explicitlyGlobal()) ret = doubleColon; QStringList identifiers; if (m_index) { identifiers.reserve(cd->identifiersSize()); FOREACH_FUNCTION_STATIC(const IndexedIdentifier &index, cd->identifiers) { identifiers += index.identifier().toString(options); } } else { identifiers.reserve(dd->identifiersSize()); FOREACH_FUNCTION_STATIC(const IndexedIdentifier &index, dd->identifiers) { identifiers += index.identifier().toString(options); } } return ret + identifiers.join(doubleColon); } QualifiedIdentifier QualifiedIdentifier::merge(const QualifiedIdentifier& base) const { QualifiedIdentifier ret(base); ret.push(*this); return ret; } QualifiedIdentifier QualifiedIdentifier::operator+(const QualifiedIdentifier& rhs) const { return rhs.merge(*this); } QualifiedIdentifier& QualifiedIdentifier::operator+=(const QualifiedIdentifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const Identifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const Identifier& rhs) { push(rhs); return *this; } QualifiedIdentifier QualifiedIdentifier::operator+(const IndexedIdentifier& rhs) const { QualifiedIdentifier ret(*this); ret.push(rhs); return ret; } QualifiedIdentifier& QualifiedIdentifier::operator+=(const IndexedIdentifier& rhs) { push(rhs); return *this; } bool QualifiedIdentifier::isExpression() const { if (m_index) return cd->m_isExpression; else return dd->m_isExpression; } void QualifiedIdentifier::setIsExpression(bool is) { if (is != isExpression()) { prepareWrite(); dd->m_isExpression = is; } } bool QualifiedIdentifier::explicitlyGlobal() const { // True if started with "::" if (m_index) return cd->m_explicitlyGlobal; else return dd->m_explicitlyGlobal; } void QualifiedIdentifier::setExplicitlyGlobal(bool eg) { if (eg != explicitlyGlobal()) { prepareWrite(); dd->m_explicitlyGlobal = eg; } } bool QualifiedIdentifier::sameIdentifiers(const QualifiedIdentifier& rhs) const { if (m_index && rhs.m_index) return cd->listsEqual(*rhs.cd); else if (m_index && !rhs.m_index) return cd->listsEqual(*rhs.dd); else if (!m_index && !rhs.m_index) return dd->listsEqual(*rhs.dd); else return dd->listsEqual(*rhs.cd); } bool QualifiedIdentifier::operator==(const QualifiedIdentifier& rhs) const { if (cd == rhs.cd) return true; return hash() == rhs.hash() && sameIdentifiers(rhs); } bool QualifiedIdentifier::operator!=(const QualifiedIdentifier& rhs) const { return !operator==(rhs); } bool QualifiedIdentifier::beginsWith(const QualifiedIdentifier& other) const { uint c = count(); uint oc = other.count(); for (uint i = 0; i < c && i < oc; ++i) if (at(i) == other.at(i)) { continue; } else { return false; } return true; } struct Visitor { Visitor(KDevVarLengthArray& target, uint hash) : target(target) , hash(hash) { } bool operator()(const ConstantQualifiedIdentifierPrivate* item, uint index) const { if (item->m_hash == hash) target.append(QualifiedIdentifier(index)); return true; } KDevVarLengthArray& target; const uint hash; }; uint QualifiedIdentifier::hash() const { if (m_index) return cd->hash(); else return dd->hash(); } uint qHash(const IndexedTypeIdentifier& id) { return id.hash(); } uint qHash(const QualifiedIdentifier& id) { return id.hash(); } uint qHash(const Identifier& id) { return id.hash(); } bool QualifiedIdentifier::isQualified() const { return count() > 1 || explicitlyGlobal(); } void QualifiedIdentifier::push(const Identifier& id) { if (id.isEmpty()) return; push(IndexedIdentifier(id)); } void QualifiedIdentifier::push(const IndexedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); dd->identifiersList.append(id); } void QualifiedIdentifier::push(const QualifiedIdentifier& id) { if (id.isEmpty()) { return; } prepareWrite(); if (id.m_index) { dd->identifiersList.append(id.cd->identifiers(), id.cd->identifiersSize()); } else { dd->identifiersList.append(id.dd->identifiers(), id.dd->identifiersSize()); } if (id.explicitlyGlobal()) { setExplicitlyGlobal(true); } } void QualifiedIdentifier::pop() { prepareWrite(); if (!dd->identifiersSize()) return; dd->identifiersList.resize(dd->identifiersList.size() - 1); } void QualifiedIdentifier::clear() { prepareWrite(); dd->identifiersList.clear(); dd->m_explicitlyGlobal = false; dd->m_isExpression = false; } bool QualifiedIdentifier::isEmpty() const { if (m_index) return cd->identifiersSize() == 0; else return dd->identifiersSize() == 0; } int QualifiedIdentifier::count() const { if (m_index) return cd->identifiersSize(); else return dd->identifiersSize(); } Identifier QualifiedIdentifier::first() const { return indexedFirst().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedFirst() const { if ((m_index && cd->identifiersSize() == 0) || (!m_index && dd->identifiersSize() == 0)) return IndexedIdentifier(); else return indexedAt(0); } Identifier QualifiedIdentifier::last() const { return indexedLast().identifier(); } IndexedIdentifier QualifiedIdentifier::indexedLast() const { uint c = count(); if (c) return indexedAt(c - 1); else return IndexedIdentifier(); } Identifier QualifiedIdentifier::top() const { return last(); } QualifiedIdentifier QualifiedIdentifier::mid(int pos, int len) const { QualifiedIdentifier ret; if (pos == 0) ret.setExplicitlyGlobal(explicitlyGlobal()); int cnt = ( int )count(); if (len == -1) len = cnt - pos; if (pos + len > cnt) len -= cnt - (pos + len); for (int a = pos; a < pos + len; a++) ret.push(at(a)); return ret; } Identifier QualifiedIdentifier::at(int i) const { return indexedAt(i).identifier(); } IndexedIdentifier QualifiedIdentifier::indexedAt(int i) const { if (m_index) { Q_ASSERT(i >= 0 && i < ( int )cd->identifiersSize()); return cd->identifiers()[i]; } else { Q_ASSERT(i >= 0 && i < ( int )dd->identifiersSize()); return dd->identifiers()[i]; } } void QualifiedIdentifier::makeConstant() const { if (m_index) return; m_index = qualifiedidentifierRepository()->index(QualifiedIdentifierItemRequest(*dd)); delete dd; cd = qualifiedidentifierRepository()->itemFromIndex(m_index); } void QualifiedIdentifier::prepareWrite() { if (m_index) { const QualifiedIdentifierPrivate* oldCc = cd; dd = new QualifiedIdentifierPrivate; dd->m_explicitlyGlobal = oldCc->m_explicitlyGlobal; dd->m_isExpression = oldCc->m_isExpression; dd->m_hash = oldCc->m_hash; dd->copyListsFrom(*oldCc); m_index = 0; } dd->clearHash(); } uint IndexedTypeIdentifier::hash() const { quint32 bitfields = static_cast(m_isConstant) | (m_isReference << 1) | (m_isRValue << 2) | (m_isVolatile << 3) | (m_pointerDepth << 4) | (m_pointerConstMask << 9); return KDevHash() << m_identifier.index() << bitfields; } bool IndexedTypeIdentifier::operator==(const IndexedTypeIdentifier& rhs) const { return m_identifier == rhs.m_identifier && m_isConstant == rhs.m_isConstant && m_isReference == rhs.m_isReference && m_isRValue == rhs.m_isRValue && m_isVolatile == rhs.m_isVolatile && m_pointerConstMask == rhs.m_pointerConstMask && m_pointerDepth == rhs.m_pointerDepth; } bool IndexedTypeIdentifier::operator!=(const IndexedTypeIdentifier& rhs) const { return !operator==(rhs); } bool IndexedTypeIdentifier::isReference() const { return m_isReference; } void IndexedTypeIdentifier::setIsReference(bool isRef) { m_isReference = isRef; } bool IndexedTypeIdentifier::isRValue() const { return m_isRValue; } void IndexedTypeIdentifier::setIsRValue(bool isRVal) { m_isRValue = isRVal; } bool IndexedTypeIdentifier::isConstant() const { return m_isConstant; } void IndexedTypeIdentifier::setIsConstant(bool isConst) { m_isConstant = isConst; } bool IndexedTypeIdentifier::isVolatile() const { return m_isVolatile; } void IndexedTypeIdentifier::setIsVolatile(bool isVolatile) { m_isVolatile = isVolatile; } int IndexedTypeIdentifier::pointerDepth() const { return m_pointerDepth; } void IndexedTypeIdentifier::setPointerDepth(int depth) { Q_ASSERT(depth <= 23 && depth >= 0); ///Clear the mask in removed fields for (int s = depth; s < ( int )m_pointerDepth; ++s) setIsConstPointer(s, false); m_pointerDepth = depth; } bool IndexedTypeIdentifier::isConstPointer(int depthNumber) const { return m_pointerConstMask & (1 << depthNumber); } void IndexedTypeIdentifier::setIsConstPointer(int depthNumber, bool constant) { if (constant) m_pointerConstMask |= (1 << depthNumber); else m_pointerConstMask &= (~(1 << depthNumber)); } QString IndexedTypeIdentifier::toString(IdentifierStringFormattingOptions options) const { QString ret; if (isConstant()) ret += QLatin1String("const "); if (isVolatile()) ret += QLatin1String("volatile "); ret += m_identifier.identifier().toString(options); for (int a = 0; a < pointerDepth(); ++a) { ret += QLatin1Char('*'); if (isConstPointer(a)) ret += QLatin1String("const"); } if (isRValue()) ret += QLatin1String("&&"); else if (isReference()) ret += QLatin1Char('&'); return ret; } IndexedTypeIdentifier::IndexedTypeIdentifier(const IndexedQualifiedIdentifier& identifier) : m_identifier(identifier) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedTypeIdentifier::IndexedTypeIdentifier(const QString& identifier, bool isExpression) : m_identifier(QualifiedIdentifier(identifier, isExpression)) , m_isConstant(false) , m_isReference(false) , m_isRValue(false) , m_isVolatile(false) , m_pointerDepth(0) , m_pointerConstMask(0) { } IndexedIdentifier::IndexedIdentifier() : m_index(emptyConstantIdentifierPrivateIndex()) { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedIdentifier::IndexedIdentifier(const Identifier& id) : m_index(id.index()) { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedIdentifier::IndexedIdentifier(const IndexedIdentifier& rhs) : m_index(rhs.m_index) { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedIdentifier::IndexedIdentifier(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { rhs.m_index = emptyConstantIdentifierPrivateIndex(); } IndexedIdentifier::~IndexedIdentifier() { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedIdentifier& IndexedIdentifier::operator=(const Identifier& id) { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } m_index = id.index(); if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(IndexedIdentifier&& rhs) Q_DECL_NOEXCEPT { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "decreasing"; ) decrease(identifierRepository()->dynamicItemFromIndexSimple(rhs.m_index)->m_refCount, rhs.m_index); } m_index = rhs.m_index; rhs.m_index = emptyConstantIdentifierPrivateIndex(); if (shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(identifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "increasing"; ) increase(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } return *this; } IndexedIdentifier& IndexedIdentifier::operator=(const IndexedIdentifier& id) { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); decrease(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } m_index = id.m_index; if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(identifierRepository()->mutex()); increase(identifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } return *this; } bool IndexedIdentifier::operator==(const IndexedIdentifier& rhs) const { return m_index == rhs.m_index; } bool IndexedIdentifier::operator!=(const IndexedIdentifier& rhs) const { return m_index != rhs.m_index; } bool IndexedIdentifier::operator==(const Identifier& id) const { return m_index == id.index(); } Identifier IndexedIdentifier::identifier() const { return Identifier(m_index); } IndexedIdentifier::operator Identifier() const { return Identifier(m_index); } bool IndexedQualifiedIdentifier::isValid() const { return m_index != emptyConstantQualifiedIdentifierPrivateIndex(); } bool IndexedQualifiedIdentifier::isEmpty() const { return m_index == emptyConstantQualifiedIdentifierPrivateIndex(); } int cnt = 0; IndexedQualifiedIdentifier IndexedTypeIdentifier::identifier() const { return m_identifier; } void IndexedTypeIdentifier::setIdentifier(const IndexedQualifiedIdentifier& id) { m_identifier = id; } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier() : m_index(emptyConstantQualifiedIdentifierPrivateIndex()) { ifDebug(qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << m_index; ) if (shouldDoDUChainReferenceCounting(this)) { ifDebug(qCDebug(LANGUAGE) << "increasing"; ) //qCDebug(LANGUAGE) << "(" << ++cnt << ")" << this << identifier().toString() << "inc" << index; QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const QualifiedIdentifier& id) : m_index(id.index()) { ifDebug(qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << m_index; ) if (shouldDoDUChainReferenceCounting(this)) { ifDebug(qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(const IndexedQualifiedIdentifier& id) : m_index(id.m_index) { ifDebug(qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << m_index; ) if (shouldDoDUChainReferenceCounting(this)) { ifDebug(qCDebug(LANGUAGE) << "increasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } IndexedQualifiedIdentifier::IndexedQualifiedIdentifier(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT : m_index(rhs.m_index) { rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const QualifiedIdentifier& id) { ifDebug(qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << m_index; ) if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); m_index = id.index(); ifDebug(qCDebug(LANGUAGE) << m_index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } else { m_index = id.index(); } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(const IndexedQualifiedIdentifier& rhs) { ifDebug(qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << m_index; ) if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); m_index = rhs.m_index; ifDebug(qCDebug(LANGUAGE) << m_index << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } else { m_index = rhs.m_index; } return *this; } IndexedQualifiedIdentifier& IndexedQualifiedIdentifier::operator=(IndexedQualifiedIdentifier&& rhs) Q_DECL_NOEXCEPT { if (shouldDoDUChainReferenceCounting(this)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } else if (shouldDoDUChainReferenceCounting(&rhs)) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "decreasing"; ) decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(rhs.m_index)->m_refCount, rhs.m_index); } m_index = rhs.m_index; rhs.m_index = emptyConstantQualifiedIdentifierPrivateIndex(); if (shouldDoDUChainReferenceCounting(this) && !(shouldDoDUChainReferenceCounting(&rhs))) { QMutexLocker lock(qualifiedidentifierRepository()->mutex()); ifDebug(qCDebug(LANGUAGE) << "increasing"; ) increase(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } return *this; } IndexedQualifiedIdentifier::~IndexedQualifiedIdentifier() { ifDebug(qCDebug(LANGUAGE) << "(" << ++cnt << ")" << identifier().toString() << index; ) if (shouldDoDUChainReferenceCounting(this)) { ifDebug(qCDebug(LANGUAGE) << index << "decreasing"; ) QMutexLocker lock(qualifiedidentifierRepository()->mutex()); decrease(qualifiedidentifierRepository()->dynamicItemFromIndexSimple(m_index)->m_refCount, m_index); } } bool IndexedQualifiedIdentifier::operator==(const IndexedQualifiedIdentifier& rhs) const { return m_index == rhs.m_index; } bool IndexedQualifiedIdentifier::operator==(const QualifiedIdentifier& id) const { return m_index == id.index(); } QualifiedIdentifier IndexedQualifiedIdentifier::identifier() const { return QualifiedIdentifier(m_index); } IndexedQualifiedIdentifier::operator QualifiedIdentifier() const { return QualifiedIdentifier(m_index); } void initIdentifierRepository() { emptyConstantIdentifierPrivateIndex(); emptyConstantIdentifierPrivate(); emptyConstantQualifiedIdentifierPrivateIndex(); emptyConstantQualifiedIdentifierPrivate(); } } QDebug operator<<(QDebug s, const KDevelop::Identifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } QDebug operator<<(QDebug s, const KDevelop::QualifiedIdentifier& identifier) { s.nospace() << identifier.toString(); return s.space(); } diff --git a/kdevplatform/language/duchain/namespacealiasdeclaration.h b/kdevplatform/language/duchain/namespacealiasdeclaration.h index b46f26d33d..9015c3cb17 100644 --- a/kdevplatform/language/duchain/namespacealiasdeclaration.h +++ b/kdevplatform/language/duchain/namespacealiasdeclaration.h @@ -1,89 +1,89 @@ /* This file is part of KDevelop Copyright 2007 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 KDEVPLATFORM_NAMESPACEALIASDECLARATION_H #define KDEVPLATFORM_NAMESPACEALIASDECLARATION_H #include "declaration.h" #include "declarationdata.h" namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT NamespaceAliasDeclarationData : public DeclarationData { public: NamespaceAliasDeclarationData() {} NamespaceAliasDeclarationData(const NamespaceAliasDeclarationData& rhs) : DeclarationData(rhs) , m_importIdentifier(rhs.m_importIdentifier) { } IndexedQualifiedIdentifier m_importIdentifier; //The identifier that was imported }; /** * A class which represents a "using namespace" statement, or a "namespace A = B" statement. * * This class is used by the duchain search process to transparently transform the search according to * namespace aliases and namespace imports. * * A namespace import declaration must have an identifier that equals globalImportIdentifier. * * If the identifier of the declaration does not equal globalImportIdentifier, then the declaration * represents a namespace alias, where the name of the alias equals the declaration. In that case, * the declaration is additionally added to the persistent symbol table with its real scope and globalAliasIdentifer * appended, to allow an efficient lookup. */ class KDEVPLATFORMLANGUAGE_EXPORT NamespaceAliasDeclaration : public Declaration { public: NamespaceAliasDeclaration(const NamespaceAliasDeclaration& rhs); NamespaceAliasDeclaration(const RangeInRevision& range, DUContext* context); explicit NamespaceAliasDeclaration(NamespaceAliasDeclarationData& data); ~NamespaceAliasDeclaration() override; ///A NamespaceAliasDeclaration cannot have a type, so setAbstractType does nothing here. void setAbstractType(AbstractType::Ptr type) override; /**The identifier that was imported.*/ QualifiedIdentifier importIdentifier() const; /** * The identifier must be absolute (Resolve it before setting it!) * Although the identifier is global, the explicitlyGlobal() member must not be set */ void setImportIdentifier(const QualifiedIdentifier& id); void setInSymbolTable(bool inSymbolTable) override; enum { Identity = 13 }; - typedef Declaration BaseClass; + using BaseClass = Declaration; QString toString() const override; private: void unregisterAliasIdentifier(); void registerAliasIdentifier(); Declaration* clonePrivate() const override; DUCHAIN_DECLARE_DATA(NamespaceAliasDeclaration) }; } #endif // KDEVPLATFORM_NAMESPACEALIASDECLARATION_H diff --git a/kdevplatform/language/duchain/navigation/abstractincludenavigationcontext.h b/kdevplatform/language/duchain/navigation/abstractincludenavigationcontext.h index 527c740a31..2055bc7190 100644 --- a/kdevplatform/language/duchain/navigation/abstractincludenavigationcontext.h +++ b/kdevplatform/language/duchain/navigation/abstractincludenavigationcontext.h @@ -1,81 +1,81 @@ /* Copyright 2007 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 KDEVPLATFORM_ABSTRACTINCLUDENAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTINCLUDENAVIGATIONCONTEXT_H #include "abstractnavigationcontext.h" #include "../../util/includeitem.h" #include "../../duchain/parsingenvironment.h" #include namespace KDevelop { /** * Abstract navigation context for file includes. * * Example usage: * \code * namespace LANG { * class IncludeNavigationContext : public AbstractIncludeNavigationContext * { * public: * IncludeNavigationContext(const IncludeItem& item, TopDuContextPointer topContext) * : AbstractIncludeNavigationContext(item, topContext, KDevelop::LANGParsingEnvironment) {} * protected: * virtual void getFileInfo(KDevelop::TopDUContext* duchain) * { * // write language dependent stuff via modifyHtml() * } * }; * } * \endcode */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractIncludeNavigationContext : public AbstractNavigationContext { Q_OBJECT public: AbstractIncludeNavigationContext(const IncludeItem& item, const TopDUContextPointer& topContext, const ParsingEnvironmentType& type); QString html(bool shorten) override; QString name() const override; protected: /// Overwrite this to add language dependent information for a given file. /// By default only "included by" and "includes" /// NOTE: You should always append a newline (
) if you write anything. virtual void getFileInfo(KDevelop::TopDUContext* duchain); ///Should return true if this declaration should be shown, and false if not ///The duchain is locked when this is called virtual bool filterDeclaration(Declaration* decl); private: /// Only environments with this type will be considered ParsingEnvironmentType m_type; - typedef QPair IdentifierPair; + using IdentifierPair = QPair; ///@param first must initially be true void addDeclarationsFromContext(KDevelop::DUContext* ctx, bool& first, QVector& addedDeclarations, const QString& indent = {}); IncludeItem m_item; }; } #endif // KDEVPLATFORM_ABSTRACTINCLUDENAVIGATIONCONTEXT_H diff --git a/kdevplatform/language/duchain/navigation/abstractnavigationcontext.h b/kdevplatform/language/duchain/navigation/abstractnavigationcontext.h index 22c1ab1c2e..6583f88d1a 100644 --- a/kdevplatform/language/duchain/navigation/abstractnavigationcontext.h +++ b/kdevplatform/language/duchain/navigation/abstractnavigationcontext.h @@ -1,185 +1,185 @@ /* Copyright 2007 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 KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #include #include #include "../indexeddeclaration.h" #include "navigationaction.h" namespace KDevelop { /** A helper-class for elegant colorization of html-strings . * * Initialize it with a html-color like "990000". and colorize strings * using operator() */ struct KDEVPLATFORMLANGUAGE_EXPORT Colorizer { enum FormattingFlag { Nothing = 0x0, Bold = 0x1, Italic = 0x2, Fixed = 0x4 }; Q_DECLARE_FLAGS(Formatting, FormattingFlag) explicit Colorizer(const QString& color, Formatting formatting = Nothing) : m_color(color) , m_formatting(formatting) { } QString operator()(const QString& str) const; QString m_color; Formatting m_formatting; }; class AbstractNavigationContext; -typedef QExplicitlySharedDataPointer NavigationContextPointer; +using NavigationContextPointer = QExplicitlySharedDataPointer; class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationContext : public QObject , public QSharedData { Q_OBJECT public: explicit AbstractNavigationContext(const TopDUContextPointer& topContext = TopDUContextPointer(), AbstractNavigationContext* previousContext = nullptr); ~AbstractNavigationContext() override; void nextLink(); void previousLink(); int linkCount() const; void up(); void down(); QString prefix() const; QString suffix() const; void setPrefixSuffix(const QString& prefix, const QString& suffix); NavigationContextPointer accept(); NavigationContextPointer back(); NavigationContextPointer accept(IndexedDeclaration decl); NavigationContextPointer acceptLink(const QString& link); NavigationAction currentAction() const; virtual QString name() const = 0; ///Here the context can return html to be displayed. ///NOTE: The DUChain must be locked while this is called. virtual QString html(bool shorten = false); ///Here the context can return a widget to be displayed. ///The widget stays owned by this navigation-context. ///The widget may have a signal "navigateDeclaration(KDevelop::IndexedDeclaration)". ///If that signal is emitted, the new declaration is navigated in the navigation-wdiget. virtual QWidget* widget() const; ///Whether the widget returned by widget() should take the maximum possible spsace. ///The default implementation returns true. virtual bool isWidgetMaximized() const; ///Returns whether this context's string has already been computed, and is up to date. ///After clear() was called, this returns false again. bool alreadyComputed() const; TopDUContextPointer topContext() const; void setTopContext(const TopDUContextPointer& context); void executeLink(const QString& link); NavigationContextPointer execute(const NavigationAction& action); Q_SIGNALS: void contentsChanged(); protected: /// Returns the html font-size prefix (aka. <small> or similar) for the given mode QString fontSizePrefix(bool shorten) const; /// Returns the html font-size suffix (aka. <small> or similar) for the given mode QString fontSizeSuffix(bool shorten) const; AbstractNavigationContext* previousContext() const; virtual void setPreviousContext(AbstractNavigationContext* previousContext); struct TextHandler { explicit TextHandler(AbstractNavigationContext* c) : context(c) { } void operator+=(const QString& str) const { context->addHtml(str); } AbstractNavigationContext* context; }; ///Override this to execute own key-actions using NavigationAction virtual NavigationContextPointer executeKeyAction(const QString& key); ///Adds given the text to currentHtml() void addHtml(const QString& html); ///Returns the html text being built in its current state QString currentHtml() const; ///Returns a convenience object that allows writing "modifyHtml() += "Hallo";" TextHandler modifyHtml() { return TextHandler(this); } //Clears the computed html and links void clear(); void addExternalHtml(const QString& text); ///Creates and registers a link to the given declaration, labeled by the given name virtual void makeLink(const QString& name, const DeclarationPointer& declaration, NavigationAction::Type actionType); ///Creates a link that executes the given action and adds it to the current context void makeLink(const QString& name, const QString& targetId, const NavigationAction& action); ///Creates a link that executes the given action and returns it QString createLink(const QString& name, const QString& targetId, const NavigationAction& action); NavigationContextPointer registerChild(const DeclarationPointer& /*declaration*/); NavigationContextPointer registerChild(AbstractNavigationContext* context); virtual QString declarationKind(const DeclarationPointer& decl); static const Colorizer typeHighlight; static const Colorizer errorHighlight; static const Colorizer labelHighlight; static const Colorizer codeHighlight; static const Colorizer propertyHighlight; static const Colorizer navigationHighlight; static const Colorizer importantHighlight; static const Colorizer commentHighlight; static const Colorizer nameHighlight; private: const QScopedPointer d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::Colorizer::Formatting) #endif diff --git a/kdevplatform/language/duchain/parsingenvironment.h b/kdevplatform/language/duchain/parsingenvironment.h index 2aec9080fb..8299d11083 100644 --- a/kdevplatform/language/duchain/parsingenvironment.h +++ b/kdevplatform/language/duchain/parsingenvironment.h @@ -1,231 +1,231 @@ /* Copyright 2007 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 KDEVPLATFORM_PARSINGENVIRONMENT_H #define KDEVPLATFORM_PARSINGENVIRONMENT_H #include #include #include "duchainbase.h" #include "topducontext.h" #include namespace KDevelop { /** * Just an enumeration of a few parsing-environment types. * Enumerate a few possible future parsing-environment types. * A parsing-environment could also have a type not in this enumeration, * the only important thing is that it's unique for the type. * * The type is needed to match ParsingEnvironment, ParsingEnvironmentFile, and ParsingEnvironmentManager together so they fit. * For example the c++-versions would have their specific type. * * The type must be unique(no other language may have the same type), * and the type must be persistent.(it must be same after restarting kdevelop) * * */ enum ParsingEnvironmentType { StandardParsingEnvironment /**< a basic standard parsing environment */, CppParsingEnvironment /**< a C++ parsing environment */, PythonParsingEnvironment /**< a python parsing environment */, CMakeParsingEnvironment /**< a CMake parsing environment */, CSharpParsingEnvironment /**< a CSharp parsing environment */, JavaParsingEnvironment /**< a JAva parsing environment */, RubyParsingEnvironment /**< a Ruby parsing environment */, PhpParsingEnvironment /**< a PHP parsing environment */ }; ///Internal struct StaticParsingEnvironmentData { TopDUContext::IndexedRecursiveImports simplifiedVisibleDeclarationsSatisfied; TopDUContext::IndexedRecursiveImports visibleDeclarationsSatisfied; TopDUContext::IndexedRecursiveImports allDeclarationsSatisfied; TopDUContext::IndexedRecursiveImports allDeclarationsAndUsesSatisfied; TopDUContext::IndexedRecursiveImports ASTSatisfied; }; /** * Use this as base-class to define new parsing-environments. * * Parsing-environments are needed for languages that create different * parsing-results depending on the environment. For example in c++, * the environment mainly consists of macros. The include-path can * be considered to be a part of the parsing-environment too, because * different files may be included using another include-path. * * The classes have to use the serialization scheme from the duchain. * Each class must be registered using REGISTER_DUCHAIN_ITEM with a unique id. * * \warning Access to this class must be serialized through du-chain locking * */ class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironment { public: ParsingEnvironment(); virtual ~ParsingEnvironment(); ///@see ParsingEnvironmentType virtual int type() const; }; ///The data class used for storing. Use this as base-class of your custom data classes for classes derived from ///ParsingEnvironmentFile class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironmentFileData : public DUChainBaseData { public: bool m_isProxyContext = false; TopDUContext::Features m_features = TopDUContext::VisibleDeclarationsAndContexts; KDevelop::ModificationRevision m_modificationTime; ModificationRevisionSet m_allModificationRevisions; KDevelop::IndexedString m_url; KDevelop::IndexedTopDUContext m_topContext; IndexedString m_language; ///If this is not empty, it means that the cache is used instead of the implicit structure. TopDUContext::IndexedRecursiveImports m_importsCache; }; -typedef QExplicitlySharedDataPointer ParsingEnvironmentFilePointer; +using ParsingEnvironmentFilePointer = QExplicitlySharedDataPointer; /** * This represents all information about a specific parsed file that is needed * to match the file to a parsing-environment. * * It is QSharedData because it is embedded into top-du-contexts and at the same time * references may be held by ParsingEnvironmentManager. * * \warning Access to this class must be serialized through du-chain locking * */ class KDEVPLATFORMLANGUAGE_EXPORT ParsingEnvironmentFile : public DUChainBase , public QSharedData { public: ~ParsingEnvironmentFile() override; explicit ParsingEnvironmentFile(const IndexedString& url); ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url); explicit ParsingEnvironmentFile(ParsingEnvironmentFileData& data); ///@see ParsingEnvironmentType virtual int type() const; ///Should return whether this file matches into the given environment. The default-implementation always returns true. virtual bool matchEnvironment(const ParsingEnvironment* environment) const; ///Convenience-function that returns the top-context TopDUContext* topContext() const override; KDevelop::IndexedTopDUContext indexedTopContext() const; KDevelop::IndexedString url() const override; ///Can additionally use language-specific information to decide whether the top-context that has this data attached needs to be reparsed. ///The standard-implementation checks the modification-time of this file stored using setModificationRevision, and all other modification-times ///stored with addModificationRevision(..). virtual bool needsUpdate(const ParsingEnvironment* environment = nullptr) const; /** * A language-specific flag used by C++ to mark one context as a proxy of another. * If this flag is set on a context, the first imported context should be used for any computations * like searches, listing, etc. instead of using this context. * * The problems should be stored within the proxy-contexts, and optionally there may be * any count of imported proxy-contexts imported behind the content-context(This can be used for tracking problems) * * Note: This flag does not directly change the behavior of the language-independent du-chain. */ bool isProxyContext() const; ///Sets the flag void setIsProxyContext(bool); ///The features of the attached top-context. They are automatically replicated here by the top-context, so they ///are accessible even without the top-context loaded. TopDUContext::Features features() const; ///Returns the parsing-environment information of all importers of the coupled TopDUContext. This is more efficient than ///loading the top-context and finding out, because when a top-context is loaded, also all its recursive imports are loaded QList> importers() const; ///Returns the parsing-environment information of all imports of the coupled TopDUContext. This is more efficient than ///loading the top-context and finding out QList> imports() const; ///Returns true if this top-context satisfies at least the given minimum features. ///If there is static minimum features set up in ParseJob, this also checks against those. ///Features like "ForceUpdate" are treated specially. ///@p minimumFeatures The features that must be satisfied. May be an arbitrary combination of features. bool featuresSatisfied(TopDUContext::Features minimumFeatures) const; ///Should return a correctly filled ModificationRevision of the source it was created from. void setModificationRevision(const KDevelop::ModificationRevision& rev); KDevelop::ModificationRevision modificationRevision() const; ///Clears the modification times of all dependencies void clearModificationRevisions(); void addModificationRevision(const IndexedString& url, const ModificationRevision& revision); const ModificationRevisionSet& allModificationRevisions() const; void addModificationRevisions(const ModificationRevisionSet&); /// Returns the language for this top-context. If the string is empty, the language is unknown. IndexedString language() const; /// If the recursive imports of the attached TopDUContext are cached, this returns the cached imports. /// This works without loading the top-context. const TopDUContext::IndexedRecursiveImports& importsCache() const; ///Sets the language for this top-context. Each top-context should get the language assigned that can by used ///in order to load the language using ILanguageController. void setLanguage(const IndexedString& language); enum { Identity = 11 }; DUCHAIN_DECLARE_DATA(ParsingEnvironmentFile) private: friend class TopDUContext; friend class DUChainPrivate; static StaticParsingEnvironmentData* m_staticData; ///The features are managed by TopDUContext. They are set to TopDUContext::Empty when the top-context is persistently destroyed, ///so the persistent feature-satisfaction cache can be cleared. void setFeatures(TopDUContext::Features); void setTopContext(KDevelop::IndexedTopDUContext context); bool featuresMatch(KDevelop::TopDUContext::Features minimumFeatures, QSet& checked) const; void setImportsCache(const TopDUContext::IndexedRecursiveImports&); }; // TODO: why is this needed/what is it supposed to print? just the pointer value? inline QDebug operator<<(QDebug s, const QExplicitlySharedDataPointer& p) { s.nospace() << p->url(); return s.space(); } } #endif diff --git a/kdevplatform/language/duchain/persistentsymboltable.cpp b/kdevplatform/language/duchain/persistentsymboltable.cpp index dfd46c13a5..a1c771757d 100644 --- a/kdevplatform/language/duchain/persistentsymboltable.cpp +++ b/kdevplatform/language/duchain/persistentsymboltable.cpp @@ -1,472 +1,473 @@ /* This file is part of KDevelop 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 "persistentsymboltable.h" #include #include "declaration.h" #include "declarationid.h" #include "appendedlist.h" #include "serialization/itemrepository.h" #include "identifier.h" #include "ducontext.h" #include "topducontext.h" #include "duchain.h" #include "duchainlock.h" #include //For now, just _always_ use the cache const uint MinimumCountForCache = 1; namespace { QDebug fromTextStream(const QTextStream& out) { if (out.device()) return { out.device() }; return { out.string() }; } } namespace KDevelop { Utils::BasicSetRepository* RecursiveImportCacheRepository::repository() { static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral( "Recursive Imports Cache"), nullptr, false); return &recursiveImportCacheRepositoryObject; } DEFINE_LIST_MEMBER_HASH(PersistentSymbolTableItem, declarations, IndexedDeclaration) class PersistentSymbolTableItem { public: PersistentSymbolTableItem() : centralFreeItem(-1) { initializeAppendedLists(); } PersistentSymbolTableItem(const PersistentSymbolTableItem& rhs, bool dynamic = true) : id(rhs.id) , centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~PersistentSymbolTableItem() { freeAppendedLists(); } inline unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return id.index(); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(PersistentSymbolTableItem); } IndexedQualifiedIdentifier id; int centralFreeItem; START_APPENDED_LISTS(PersistentSymbolTableItem); APPENDED_LIST_FIRST(PersistentSymbolTableItem, IndexedDeclaration, declarations); END_APPENDED_LISTS(PersistentSymbolTableItem, declarations); }; class PersistentSymbolTableRequestItem { public: PersistentSymbolTableRequestItem(const PersistentSymbolTableItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(PersistentSymbolTableItem* item) const { new (item) PersistentSymbolTableItem(m_item, false); } static void destroy(PersistentSymbolTableItem* item, KDevelop::AbstractItemRepository&) { item->~PersistentSymbolTableItem(); } static bool persistent(const PersistentSymbolTableItem*) { return true; //Nothing to do } bool equals(const PersistentSymbolTableItem* item) const { return m_item.id == item->id; } const PersistentSymbolTableItem& m_item; }; template struct CacheEntry { - typedef KDevVarLengthArray Data; - typedef QHash DataHash; + using Data = KDevVarLengthArray; + using DataHash = QHash; DataHash m_hash; }; class PersistentSymbolTablePrivate { public: PersistentSymbolTablePrivate() : m_declarations(QStringLiteral("Persistent Declaration Table")) { } //Maps declaration-ids to declarations ItemRepository m_declarations; QHash> m_declarationsCache; //We cache the imports so the currently used nodes are very close in memory, which leads to much better CPU cache utilization QHash m_importsCache; }; void PersistentSymbolTable::clearCache() { ENSURE_CHAIN_WRITE_LOCKED { QMutexLocker lock(d->m_declarations.mutex()); d->m_importsCache.clear(); d->m_declarationsCache.clear(); } } PersistentSymbolTable::PersistentSymbolTable() : d(new PersistentSymbolTablePrivate()) { } PersistentSymbolTable::~PersistentSymbolTable() { //Workaround for a strange destruction-order related crash duing shutdown //We just let the data leak. This doesn't hurt, as there is no meaningful destructors. // TODO: analyze and fix it // delete d; } void PersistentSymbolTable::addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_WRITE_LOCKED d->m_declarationsCache.remove(id); PersistentSymbolTableItem item; item.id = id; PersistentSymbolTableRequestItem request(item); uint index = d->m_declarations.findIndex(item); if (index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSymbolTableItem* oldItem = d->m_declarations.itemFromIndex(index); EmbeddedTreeAlgorithms alg( oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); if (alg.indexOf(declaration) != -1) return; DynamicItem editableItem = d->m_declarations.dynamicItemFromIndex(index); EmbeddedTreeAddItem add( const_cast(editableItem->declarations()), editableItem->declarationsSize(), editableItem->centralFreeItem, declaration); uint newSize = add.newItemCount(); if (newSize != editableItem->declarationsSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.declarationsList().resize(newSize); add.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); d->m_declarations.deleteItem(index); Q_ASSERT(!d->m_declarations.findIndex(request)); } else { //We're fine, the item could be added to the existing list return; } } else { item.declarationsList().append(declaration); } //This inserts the changed item d->m_declarations.index(request); } void PersistentSymbolTable::removeDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration) { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_WRITE_LOCKED d->m_declarationsCache.remove(id); Q_ASSERT(!d->m_declarationsCache.contains(id)); PersistentSymbolTableItem item; item.id = id; PersistentSymbolTableRequestItem request(item); uint index = d->m_declarations.findIndex(item); if (index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSymbolTableItem* oldItem = d->m_declarations.itemFromIndex(index); EmbeddedTreeAlgorithms alg( oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem); if (alg.indexOf(declaration) == -1) return; DynamicItem editableItem = d->m_declarations.dynamicItemFromIndex(index); EmbeddedTreeRemoveItem remove( const_cast(editableItem->declarations()), editableItem->declarationsSize(), editableItem->centralFreeItem, declaration); uint newSize = remove.newItemCount(); if (newSize != editableItem->declarationsSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.declarationsList().resize(newSize); remove.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem); d->m_declarations.deleteItem(index); Q_ASSERT(!d->m_declarations.findIndex(request)); } else { //We're fine, the item could be added to the existing list return; } } //This inserts the changed item if (item.declarationsSize()) d->m_declarations.index(request); } struct DeclarationCacheVisitor { explicit DeclarationCacheVisitor(KDevVarLengthArray& _cache) : cache(_cache) { } bool operator()(const IndexedDeclaration& decl) const { cache.append(decl); return true; } KDevVarLengthArray& cache; }; PersistentSymbolTable::FilteredDeclarationIterator PersistentSymbolTable::filteredDeclarations( const IndexedQualifiedIdentifier& id, const TopDUContext::IndexedRecursiveImports& visibility) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED Declarations decls = declarations(id).iterator(); CachedIndexedRecursiveImports cachedImports; QHash::const_iterator it = d->m_importsCache.constFind(visibility); if (it != d->m_importsCache.constEnd()) { cachedImports = *it; } else { cachedImports = CachedIndexedRecursiveImports(visibility.set().stdSet()); d->m_importsCache.insert(visibility, cachedImports); } if (decls.dataSize() > MinimumCountForCache) { //Do visibility caching CacheEntry& cached(d->m_declarationsCache[id]); CacheEntry::DataHash::const_iterator cacheIt = cached.m_hash.constFind(visibility); if (cacheIt != cached.m_hash.constEnd()) return FilteredDeclarationIterator(Declarations::Iterator(cacheIt->constData(), cacheIt->size(), -1), cachedImports); CacheEntry::DataHash::iterator insertIt = cached.m_hash.insert(visibility, KDevVarLengthArray()); KDevVarLengthArray& cache(*insertIt); { - typedef ConvenientEmbeddedSetTreeFilterVisitor FilteredDeclarationCacheVisitor; + DeclarationCacheVisitor>; //The visitor visits all the declarations from within its constructor DeclarationCacheVisitor v(cache); FilteredDeclarationCacheVisitor visitor(v, decls.iterator(), cachedImports); } return FilteredDeclarationIterator(Declarations::Iterator(cache.constData(), cache.size(), -1), cachedImports, true); } else { return FilteredDeclarationIterator(decls.iterator(), cachedImports); } } PersistentSymbolTable::Declarations PersistentSymbolTable::declarations(const IndexedQualifiedIdentifier& id) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED PersistentSymbolTableItem item; item.id = id; uint index = d->m_declarations.findIndex(item); if (index) { const PersistentSymbolTableItem* repositoryItem = d->m_declarations.itemFromIndex(index); return PersistentSymbolTable::Declarations(repositoryItem->declarations(), repositoryItem->declarationsSize(), repositoryItem->centralFreeItem); } else { return PersistentSymbolTable::Declarations(); } } void PersistentSymbolTable::declarations(const IndexedQualifiedIdentifier& id, uint& countTarget, const IndexedDeclaration*& declarationsTarget) const { QMutexLocker lock(d->m_declarations.mutex()); ENSURE_CHAIN_READ_LOCKED PersistentSymbolTableItem item; item.id = id; uint index = d->m_declarations.findIndex(item); if (index) { const PersistentSymbolTableItem* repositoryItem = d->m_declarations.itemFromIndex(index); countTarget = repositoryItem->declarationsSize(); declarationsTarget = repositoryItem->declarations(); } else { countTarget = 0; declarationsTarget = nullptr; } } struct DebugVisitor { explicit DebugVisitor(const QTextStream& _out) : out(_out) { } bool operator()(const PersistentSymbolTableItem* item) { QDebug qout = fromTextStream(out); QualifiedIdentifier id(item->id.identifier()); if (identifiers.contains(id)) { qout << "identifier" << id.toString() << "appears for" << identifiers[id] << "th time"; } ++identifiers[id]; for (uint a = 0; a < item->declarationsSize(); ++a) { IndexedDeclaration decl(item->declarations()[a]); if (!decl.isDummy()) { if (declarations.contains(decl)) { qout << "declaration found for multiple identifiers. Previous identifier:" << declarations[decl].toString() << "current identifier:" << id.toString() << endl; } else { declarations.insert(decl, id); } } if (decl.data() && decl.data()->qualifiedIdentifier() != item->id.identifier()) { qout << decl.data()->url().str() << "declaration" << decl.data()->qualifiedIdentifier() << "is registered as" << item->id.identifier() << endl; } const QString url = IndexedTopDUContext(decl.topContextIndex()).url().str(); if (!decl.data() && !decl.isDummy()) { qout << "Item in symbol-table is invalid:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << url << endl; } else { qout << "Item in symbol-table:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" << url; if (auto d = decl.data()) { qout << "- range:" << d->range(); } else { qout << "- null declaration"; } qout << endl; } } return true; } const QTextStream& out; QHash identifiers; QHash declarations; }; void PersistentSymbolTable::dump(const QTextStream& out) { { QMutexLocker lock(d->m_declarations.mutex()); QDebug qout = fromTextStream(out); DebugVisitor v(out); d->m_declarations.visitAllItems(v); qout << "Statistics:" << endl; qout << d->m_declarations.statistics() << endl; } } PersistentSymbolTable& PersistentSymbolTable::self() { static PersistentSymbolTable ret; return ret; } } diff --git a/kdevplatform/language/duchain/persistentsymboltable.h b/kdevplatform/language/duchain/persistentsymboltable.h index d68cea784e..9fb80f7921 100644 --- a/kdevplatform/language/duchain/persistentsymboltable.h +++ b/kdevplatform/language/duchain/persistentsymboltable.h @@ -1,161 +1,163 @@ /* This file is part of KDevelop 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. */ #ifndef KDEVPLATFORM_PERSISTENTSYMBOLTABLE_H #define KDEVPLATFORM_PERSISTENTSYMBOLTABLE_H #include #include #include #include "indexeddeclaration.h" #include "ducontext.h" #include "topducontext.h" namespace KDevelop { class Declaration; class IndexedDeclaration; class IndexedDUContext; class DeclarationId; class TopDUContext; class IndexedQualifiedIdentifier; ///@todo move into own header class KDEVPLATFORMLANGUAGE_EXPORT IndexedDeclarationHandler { public: inline static int leftChild(const IndexedDeclaration& m_data) { return (( int )(m_data.dummyData().first)) - 1; } inline static void setLeftChild(IndexedDeclaration& m_data, int child) { m_data.setDummyData(qMakePair(( uint )(child + 1), m_data.dummyData().second)); } inline static int rightChild(const IndexedDeclaration& m_data) { return (( int )m_data.dummyData().second) - 1; } inline static void setRightChild(IndexedDeclaration& m_data, int child) { m_data.setDummyData(qMakePair(m_data.dummyData().first, ( uint )(child + 1))); } inline static void createFreeItem(IndexedDeclaration& data) { data = IndexedDeclaration(); data.setIsDummy(true); data.setDummyData(qMakePair(0u, 0u)); //Since we subtract 1, this equals children -1, -1 } //Copies this item into the given one inline static void copyTo(const IndexedDeclaration& m_data, IndexedDeclaration& data) { data = m_data; } inline static bool isFree(const IndexedDeclaration& m_data) { return m_data.isDummy(); } inline static bool equals(const IndexedDeclaration& m_data, const IndexedDeclaration& rhs) { return m_data == rhs; } }; struct DeclarationTopContextExtractor { inline static IndexedTopDUContext extract(const IndexedDeclaration& decl) { return decl.indexedTopContext(); } }; struct DUContextTopContextExtractor { inline static IndexedTopDUContext extract(const IndexedDUContext& ctx) { return ctx.indexedTopContext(); } }; struct KDEVPLATFORMLANGUAGE_EXPORT RecursiveImportCacheRepository { static Utils::BasicSetRepository* repository(); }; /** * Global symbol-table that is stored to disk, and allows retrieving declarations that currently are not loaded to memory. * */ class KDEVPLATFORMLANGUAGE_EXPORT PersistentSymbolTable { public: /// Constructor. PersistentSymbolTable(); /// Destructor. ~PersistentSymbolTable(); ///Adds declaration @p declaration with id @p id to the symbol table ///@warning DUChain must be write locked void addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration); ///Adds declaration @p declaration with id @p id to the symbol table ///@warning DUChain must be write locked void removeDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration); ///Retrieves all the declarations for a given IndexedQualifiedIdentifier in an efficient way. ///@param id The IndexedQualifiedIdentifier for which the declarations should be retrieved ///@param count A reference that will be filled with the count of retrieved declarations ///@param declarations A reference to a pointer, that will be filled with a pointer to the retrieved declarations. ///@warning DUChain must be read locked as long as the returned data is used void declarations(const IndexedQualifiedIdentifier& id, uint& count, const IndexedDeclaration*& declarations) const; - typedef ConstantConvenientEmbeddedSet Declarations; + using Declarations = ConstantConvenientEmbeddedSet; ///Retrieves all the declarations for a given IndexedQualifiedIdentifier in an efficient way, and returns ///them in a structure that is more convenient than declarations(). ///@param id The IndexedQualifiedIdentifier for which the declarations should be retrieved ///@warning DUChain must be read locked as long as the returned data is used Declarations declarations(const IndexedQualifiedIdentifier& id) const; - typedef Utils::StorableSet CachedIndexedRecursiveImports; + using CachedIndexedRecursiveImports = + Utils::StorableSet; - typedef ConvenientEmbeddedSetTreeFilterIterator FilteredDeclarationIterator; + using FilteredDeclarationIterator = + ConvenientEmbeddedSetTreeFilterIterator; ///Retrieves an iterator to all declarations of the given id, filtered by the visilibity given through @a visibility ///This is very efficient since it uses a cache ///The returned iterator is valid as long as the duchain read lock is held FilteredDeclarationIterator filteredDeclarations(const IndexedQualifiedIdentifier& id, const TopDUContext::IndexedRecursiveImports& visibility) const; static PersistentSymbolTable& self(); //Very expensive: Checks for problems in the symbol table void dump(const QTextStream& out); //Clears the internal cache. Should be called regularly to save memory //The duchain must be read-locked void clearCache(); private: // cannot use QScopedPointer yet, see comment in ~PersistentSymbolTable() class PersistentSymbolTablePrivate* const d; }; } #endif diff --git a/kdevplatform/language/duchain/tests/bench_hashes.cpp b/kdevplatform/language/duchain/tests/bench_hashes.cpp index 98fd5e1494..56db19c71d 100644 --- a/kdevplatform/language/duchain/tests/bench_hashes.cpp +++ b/kdevplatform/language/duchain/tests/bench_hashes.cpp @@ -1,321 +1,321 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * 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 "bench_hashes.h" #include #include #include #include #include #include #include // similar to e.g. modificationrevision.cpp struct DataT { QDateTime a; QDateTime b; }; -typedef QPair DataPair; -typedef QVector InputData; +using DataPair = QPair; +using InputData = QVector; struct IndexedStringHash { inline uint operator()(const KDevelop::IndexedString& str) const { return str.hash(); } }; -typedef std::unordered_map StlHash; +using StlHash = std::unordered_map; inline void insertData(StlHash& hash, const InputData& data) { for (const DataPair& pair : data) { hash.insert(std::make_pair(pair.first, pair.second)); } } -typedef QHash QStringHash; +using QStringHash = QHash; inline void insertData(QStringHash& hash, const InputData& data) { for (const DataPair& pair : data) { hash.insert(pair.first, pair.second); } } QTEST_GUILESS_MAIN(BenchHashes) using namespace KDevelop; Q_DECLARE_METATYPE(InputData) void BenchHashes::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType(); } void BenchHashes::cleanupTestCase() { TestCore::shutdown(); } void BenchHashes::feedData() { QTest::addColumn("useStl"); QTest::addColumn("data"); InputData data; const QVector sizes{100, 1000, 10000, 100000}; for (int size : sizes) { for (int i = data.size(); i < size; ++i) { data << qMakePair(IndexedString(QString::number(i)), DataT()); } QCOMPARE(data.size(), size); QTest::newRow(qPrintable(QStringLiteral("unordered_map-%1").arg(size))) << true << data; QTest::newRow(qPrintable(QStringLiteral("qhash-%1").arg(size))) << false << data; } } void BenchHashes::insert() { QFETCH(bool, useStl); QFETCH(InputData, data); if (useStl) { QBENCHMARK { StlHash hash; insertData(hash, data); } } else { QBENCHMARK { QStringHash hash; insertData(hash, data); } } } void BenchHashes::insert_data() { feedData(); } void BenchHashes::find() { QFETCH(bool, useStl); QFETCH(InputData, data); if (useStl) { StlHash hash; insertData(hash, data); QBENCHMARK { foreach (const DataPair& pair, data) { ( void ) hash.find(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach (const DataPair& pair, data) { ( void ) hash.find(pair.first); } } } } void BenchHashes::find_data() { feedData(); } void BenchHashes::constFind() { QFETCH(bool, useStl); QFETCH(InputData, data); if (useStl) { StlHash hash; insertData(hash, data); const StlHash& constHash = hash; QBENCHMARK { foreach (const DataPair& pair, data) { ( void ) constHash.find(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach (const DataPair& pair, data) { ( void ) hash.constFind(pair.first); } } } } void BenchHashes::constFind_data() { feedData(); } void BenchHashes::remove() { QFETCH(bool, useStl); QFETCH(InputData, data); if (useStl) { StlHash hash; insertData(hash, data); QBENCHMARK { foreach (const DataPair& pair, data) { hash.erase(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach (const DataPair& pair, data) { hash.remove(pair.first); } } } } void BenchHashes::remove_data() { feedData(); } struct TypeRepoTestData { size_t size; void* ptr; }; /** * somewhat artificial benchmark to test speed impact if we'd ever change * the underlying data type of the TypeSystem / TypeRegister. */ void BenchHashes::typeRepo() { QFETCH(int, type); if (type == 1 || type == 2) { QVector v; for (int i = 0; i < 100; ++i) { v.append(new TypeRepoTestData); } if (type == 1) { QBENCHMARK { for (int i = 0; i < 100; ++i) { v.at(i)->size++; } } } else if (type == 2) { TypeRepoTestData** a = v.data(); QBENCHMARK { for (int i = 0; i < 100; ++i) { a[i]->size++; } } } } else if (type == 3) { QHash v; for (int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for (int i = 0; i < 100; ++i) { v.value(i)->size++; } } } else if (type == 4) { QMap v; for (int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for (int i = 0; i < 100; ++i) { v.value(i)->size++; } } } else if (type == 5) { std::unordered_map v; for (int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for (int i = 0; i < 100; ++i) { v.at(i)->size++; } } } else if (type == 6) { // for the idea, look at c++'s lexer.cpp const int vectors = 5; - typedef QPair Pair; - typedef QVarLengthArray InnerVector; + using Pair = QPair; + using InnerVector = QVarLengthArray; QVarLengthArray v; v.resize(vectors); for (int i = 0; i < 100; ++i) { v[i % vectors] << qMakePair(i, new TypeRepoTestData); } QBENCHMARK { for (int i = 0; i < 100; ++i) { foreach (const Pair& p, v.at(i % vectors)) { if (p.first == i) { p.second->size++; break; } } } } } else if (type == 0) { QBENCHMARK {} } } void BenchHashes::typeRepo_data() { QTest::addColumn("type"); QTest::newRow("noop") << 0; QTest::newRow("vector") << 1; QTest::newRow("vector-raw") << 2; QTest::newRow("qhash") << 3; QTest::newRow("qmap") << 4; QTest::newRow("unordered_map") << 5; QTest::newRow("nested-vector") << 6; } diff --git a/kdevplatform/language/duchain/tests/test_duchain.cpp b/kdevplatform/language/duchain/tests/test_duchain.cpp index 9c5e162065..97af57f062 100644 --- a/kdevplatform/language/duchain/tests/test_duchain.cpp +++ b/kdevplatform/language/duchain/tests/test_duchain.cpp @@ -1,1088 +1,1088 @@ /* * This file is part of KDevelop * * Copyright 2011-2013 Milian Wolff * Copyright 2006 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. */ #include "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include #include #include #include // needed for std::insert_iterator on windows #include //Extremely slow // #define TEST_NORMAL_IMPORTS QTEST_MAIN(TestDUChain) using namespace KDevelop; using namespace Utils; -typedef BasicSetRepository::Index Index; +using Index = BasicSetRepository::Index; struct Timer { Timer() { m_timer.start(); } qint64 elapsed() { return m_timer.nsecsElapsed(); } QElapsedTimer m_timer; }; void TestDUChain::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); CodeRepresentation::setDiskChangesForbidden(true); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } #ifndef Q_OS_WIN void TestDUChain::testStringSets() { const unsigned int setCount = 8; const unsigned int choiceCount = 40; const unsigned int itemCount = 120; BasicSetRepository rep(QStringLiteral("test repository")); // qDebug() << "Start repository-layout: \n" << rep.dumpDotGraph(); qint64 repositoryTime = 0; //Time spent on repository-operations qint64 genericTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryIntersectionTime = 0; //Time spent on repository-operations qint64 genericIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 qsetIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryUnionTime = 0; //Time spent on repository-operations qint64 genericUnionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryDifferenceTime = 0; //Time spent on repository-operations qint64 genericDifferenceTime = 0; //Time spend on equivalent operations with generic sets Set sets[setCount]; std::set realSets[setCount]; for (unsigned int a = 0; a < setCount; a++) { std::set chosenIndices; unsigned int thisCount = rand() % choiceCount; if (thisCount == 0) thisCount = 1; for (unsigned int b = 0; b < thisCount; b++) { Index choose = (rand() % itemCount) + 1; while (chosenIndices.find(choose) != chosenIndices.end()) { choose = (rand() % itemCount) + 1; } Timer t; chosenIndices.insert(chosenIndices.end(), choose); genericTime += t.elapsed(); } { Timer t; sets[a] = rep.createSet(chosenIndices); repositoryTime += t.elapsed(); } realSets[a] = chosenIndices; std::set tempSet = sets[a].stdSet(); if (tempSet != realSets[a]) { QString dbg = QStringLiteral("created set: "); for (auto it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; dbg = QStringLiteral("repo. set: "); for (auto it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; QFAIL("sets are not the same!"); } } for (int cycle = 0; cycle < 100; ++cycle) { if (cycle % 10 == 0) qDebug() << "cycle" << cycle; for (unsigned int a = 0; a < setCount; a++) { for (unsigned int b = 0; b < setCount; b++) { /// ----- SUBTRACTION/DIFFERENCE std::set _realDifference; { Timer t; std::set_difference(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator>(_realDifference, _realDifference.begin())); genericDifferenceTime += t.elapsed(); } Set _difference; { Timer t; _difference = sets[a] - sets[b]; repositoryDifferenceTime += t.elapsed(); } if (_difference.stdSet() != _realDifference) { { qDebug() << "SET a:"; QString dbg; for (auto it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg; for (auto it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _difference.stdSet(); qDebug() << "SET difference:"; QString dbg = QStringLiteral("real set: "); for (auto it = _realDifference.begin(); it != _realDifference.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; dbg = QStringLiteral("repo. set: "); for (auto it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _difference.dumpDotGraph() << "\n\n"; } QFAIL("difference sets are not the same!"); } /// ------ UNION std::set _realUnion; { Timer t; std::set_union(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator>(_realUnion, _realUnion.begin())); genericUnionTime += t.elapsed(); } Set _union; { Timer t; _union = sets[a] + sets[b]; repositoryUnionTime += t.elapsed(); } if (_union.stdSet() != _realUnion) { { qDebug() << "SET a:"; QString dbg; for (auto it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg; for (auto it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _union.stdSet(); qDebug() << "SET union:"; QString dbg = QStringLiteral("real set: "); for (auto it = _realUnion.begin(); it != _realUnion.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; dbg = QStringLiteral("repo. set: "); for (auto it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _union.dumpDotGraph() << "\n\n"; } QFAIL("union sets are not the same"); } std::set _realIntersection; /// -------- INTERSECTION { Timer t; std::set_intersection(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator>(_realIntersection, _realIntersection.begin())); genericIntersectionTime += t.elapsed(); } //Just for fun: Test how fast QSet intersections are QSet first, second; for (auto it = realSets[a].begin(); it != realSets[a].end(); ++it) { first.insert(*it); } for (auto it = realSets[b].begin(); it != realSets[b].end(); ++it) { second.insert(*it); } { Timer t; QSet i = first.intersect(second); // clazy:exclude=unused-non-trivial-variable qsetIntersectionTime += t.elapsed(); } Set _intersection; { Timer t; _intersection = sets[a] & sets[b]; repositoryIntersectionTime += t.elapsed(); } if (_intersection.stdSet() != _realIntersection) { { qDebug() << "SET a:"; QString dbg; for (auto it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg; for (auto it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _intersection.stdSet(); qDebug() << "SET intersection:"; QString dbg = QStringLiteral("real set: "); for (auto it = _realIntersection.begin(); it != _realIntersection.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; dbg = QStringLiteral("repo. set: "); for (auto it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QStringLiteral("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _intersection.dumpDotGraph() << "\n\n"; } QFAIL("intersection sets are not the same"); } } } qDebug() << "cycle " << cycle; qDebug() << "ns needed for set-building: repository-set: " << float( repositoryTime ) << " generic-set: " << float( genericTime ); qDebug() << "ns needed for intersection: repository-sets: " << float( repositoryIntersectionTime ) << " generic-set: " << float( genericIntersectionTime ) << " QSet: " << float( qsetIntersectionTime ); qDebug() << "ns needed for union: repository-sets: " << float( repositoryUnionTime ) << " generic-set: " << float( genericUnionTime ); qDebug() << "ns needed for difference: repository-sets: " << float( repositoryDifferenceTime ) << " generic-set: " << float( genericDifferenceTime ); } } #endif void TestDUChain::testSymbolTableValid() { DUChainReadLocker lock(DUChain::lock()); PersistentSymbolTable::self().dump(QTextStream(stdout)); } void TestDUChain::testIndexedStrings() { int testCount = 600000; QHash knownIndices; int a = 0; for (a = 0; a < testCount; ++a) { QString testString; int length = rand() % 10; for (int b = 0; b < length; ++b) testString.append(( char )(rand() % 6) + 'a'); QByteArray array = testString.toUtf8(); //qDebug() << "checking with" << testString; //qDebug() << "checking" << a; IndexedString indexed(array.constData(), array.size(), IndexedString::hashString(array.constData(), array.size())); QCOMPARE(indexed.str(), testString); if (knownIndices.contains(testString)) { QCOMPARE(indexed.index(), knownIndices[testString].index()); } else { knownIndices[testString] = indexed; } if (a % (testCount / 10) == 0) qDebug() << a << "of" << testCount; } qDebug() << a << "successful tests"; } struct TestContext { TestContext() { static int number = 0; ++number; DUChainWriteLocker lock(DUChain::lock()); m_context = new TopDUContext(IndexedString(QStringLiteral("/test1/%1").arg(number)), RangeInRevision()); m_normalContext = new DUContext(RangeInRevision(), m_context); DUChain::self()->addDocumentChain(m_context); Q_ASSERT(IndexedDUContext(m_context).context() == m_context); } ~TestContext() { foreach (TestContext* importer, importers) importer->unImport(QList() << this); unImport(imports); DUChainWriteLocker lock(DUChain::lock()); TopDUContextPointer tp(m_context); DUChain::self()->removeDocumentChain(static_cast(m_context)); Q_ASSERT(!tp); } void verify(QList allContexts) { { DUChainReadLocker lock(DUChain::lock()); QCOMPARE(m_context->importedParentContexts().count(), imports.count()); } //Compute a closure of all children, and verify that they are imported. QSet collected; collectImports(collected); collected.remove(this); DUChainReadLocker lock(DUChain::lock()); foreach (TestContext* context, collected) { QVERIFY(m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(m_normalContext->imports(context->m_normalContext)); #endif } //Verify that no other contexts are imported foreach (TestContext* context, allContexts) if (context != this) { QVERIFY(collected.contains(context) || !m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(collected.contains(context) || !m_normalContext->imports(context->m_normalContext, CursorInRevision::invalid())); #endif } } void collectImports(QSet& collected) { if (collected.contains(this)) return; collected.insert(this); foreach (TestContext* context, imports) context->collectImports(collected); } void import(TestContext* ctx) { if (imports.contains(ctx) || ctx == this) return; imports << ctx; ctx->importers << this; DUChainWriteLocker lock(DUChain::lock()); m_context->addImportedParentContext(ctx->m_context); #ifdef TEST_NORMAL_IMPORTS m_normalContext->addImportedParentContext(ctx->m_normalContext); #endif } void unImport(QList ctxList) { QList list; QList normalList; foreach (TestContext* ctx, ctxList) { if (!imports.contains(ctx)) continue; list << ctx->m_context; normalList << ctx->m_normalContext; imports.removeAll(ctx); ctx->importers.removeAll(this); } DUChainWriteLocker lock(DUChain::lock()); m_context->removeImportedParentContexts(list); #ifdef TEST_NORMAL_IMPORTS foreach (DUContext* ctx, normalList) m_normalContext->removeImportedParentContext(ctx); #endif } void clearImports() { { DUChainWriteLocker lock(DUChain::lock()); m_context->clearImportedParentContexts(); m_normalContext->clearImportedParentContexts(); } foreach (TestContext* ctx, imports) { imports.removeAll(ctx); ctx->importers.removeAll(this); } } QList imports; private: TopDUContext* m_context; DUContext* m_normalContext; QList importers; }; void collectReachableNodes(QSet& reachableNodes, uint currentNode) { if (!currentNode) return; reachableNodes.insert(currentNode); const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); collectReachableNodes(reachableNodes, node->leftNode()); collectReachableNodes(reachableNodes, node->rightNode()); } uint collectNaiveNodeCount(uint currentNode) { if (!currentNode) return 0; uint ret = 1; const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); ret += collectNaiveNodeCount(node->leftNode()); ret += collectNaiveNodeCount(node->rightNode()); return ret; } void TestDUChain::testImportStructure() { Timer total; qDebug() << "before: " << KDevelop::RecursiveImportRepository::repository()->dataRepository().statistics().print(); ///Maintains a naive import-structure along with a real top-context import structure, and allows comparing both. int cycles = 5; //int cycles = 100; //srand(time(NULL)); for (int t = 0; t < cycles; ++t) { QList allContexts; //Create a random structure int contextCount = 50; int verifyOnceIn = contextCount /*((contextCount*contextCount)/20)+1*/; //Verify once in every chances(not in all cases, because else the import-structure isn't built on-demand!) int clearOnceIn = contextCount; for (int a = 0; a < contextCount; a++) allContexts << new TestContext(); for (int c = 0; c < cycles; ++c) { //qDebug() << "main-cycle" << t << "sub-cycle" << c; //Add random imports and compare for (int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int importCount = rand() % 5; //qDebug() << "cnt> " << importCount; for (int i = 0; i < importCount; ++i) { //int importNr = rand() % contextCount; //qDebug() << "nmr > " << importNr; //allContexts[a]->import(allContexts[importNr]); allContexts[a]->import(allContexts[rand() % contextCount]); } for (int b = 0; b < contextCount; b++) if (rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } //Remove random imports and compare for (int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int removeCount = rand() % 3; QSet removeImports; for (int i = 0; i < removeCount; ++i) if (!allContexts[a]->imports.isEmpty()) removeImports.insert(allContexts[a]->imports[rand() % allContexts[a]->imports.count()]); allContexts[a]->unImport(removeImports.toList()); for (int b = 0; b < contextCount; b++) if (rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } for (int a = 0; a < contextCount; a++) { if (rand() % clearOnceIn == 0) { allContexts[a]->clearImports(); allContexts[a]->verify(allContexts); } } } qDebug() << "after: " << KDevelop::RecursiveImportRepository::repository()->dataRepository().statistics().print(); for (int a = 0; a < contextCount; ++a) delete allContexts[a]; allContexts.clear(); qDebug() << "after cleanup: " << KDevelop::RecursiveImportRepository::repository()->dataRepository().statistics().print(); } qDebug() << "total ns needed for import-structure test:" << float( total.elapsed()); } class TestWorker : public QObject { Q_OBJECT public Q_SLOTS: void lockForWrite() { for (int i = 0; i < 10000; ++i) { DUChainWriteLocker lock; } } void lockForRead() { for (int i = 0; i < 10000; ++i) { DUChainReadLocker lock; } } void lockForReadWrite() { for (int i = 0; i < 10000; ++i) { { DUChainReadLocker lock; } { DUChainWriteLocker lock; } } } static QSharedPointer createWorkerThread(const char* workerSlot) { auto* thread = new QThread; auto* worker = new TestWorker; connect(thread, SIGNAL(started()), worker, workerSlot); connect(thread, &QThread::finished, worker, &TestWorker::deleteLater); worker->moveToThread(thread); return QSharedPointer(thread); } }; class ThreadList : public QVector> { public: bool join(int timeout) { foreach (const QSharedPointer& thread, * this) { // quit event loop Q_ASSERT(thread->isRunning()); thread->quit(); // wait for finish if (!thread->wait(timeout)) { return false; } Q_ASSERT(thread->isFinished()); } return true; } void start() { foreach (const QSharedPointer& thread, * this) { thread->start(); } } }; void TestDUChain::testLockForWrite() { ThreadList threads; for (int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForWrite())); } threads.start(); QBENCHMARK { { DUChainWriteLocker lock; } { DUChainReadLocker lock; } } QVERIFY(threads.join(1000)); } void TestDUChain::testLockForRead() { ThreadList threads; for (int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForRead())); } threads.start(); QBENCHMARK { DUChainReadLocker lock; } QVERIFY(threads.join(1000)); } void TestDUChain::testLockForReadWrite() { ThreadList threads; for (int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForReadWrite())); } threads.start(); QBENCHMARK { DUChainWriteLocker lock; } QVERIFY(threads.join(1000)); } void TestDUChain::testProblemSerialization() { DUChain::self()->disablePersistentStorage(false); auto parent = ProblemPointer{new Problem}; parent->setDescription(QStringLiteral("parent")); auto child = ProblemPointer{new Problem}; child->setDescription(QStringLiteral("child")); parent->addDiagnostic(child); const IndexedString url("/my/test/file"); TopDUContextPointer smartTop; { // serialize DUChainWriteLocker lock; auto file = new ParsingEnvironmentFile(url); auto top = new TopDUContext(url, {}, file); top->addProblem(parent); QCOMPARE(top->problems().size(), 1); auto p = top->problems().at(0); QCOMPARE(p->description(), QStringLiteral("parent")); QCOMPARE(p->diagnostics().size(), 1); auto c = p->diagnostics().first(); QCOMPARE(c->description(), QStringLiteral("child")); DUChain::self()->addDocumentChain(top); QVERIFY(DUChain::self()->chainForDocument(url)); smartTop = top; } DUChain::self()->storeToDisk(); ProblemPointer parent_deserialized; IProblem::Ptr child_deserialized; { // deserialize DUChainWriteLocker lock; QVERIFY(!smartTop); auto top = DUChain::self()->chainForDocument(url); QVERIFY(top); smartTop = top; QCOMPARE(top->problems().size(), 1); parent_deserialized = top->problems().at(0); QCOMPARE(parent_deserialized->diagnostics().size(), 1); child_deserialized = parent_deserialized->diagnostics().first(); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); top->clearProblems(); QVERIFY(top->problems().isEmpty()); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); DUChain::self()->removeDocumentChain(top); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); QVERIFY(!smartTop); } DUChain::self()->disablePersistentStorage(true); QCOMPARE(parent->description(), QStringLiteral("parent")); QCOMPARE(child->description(), QStringLiteral("child")); QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); QCOMPARE(child_deserialized->description(), QStringLiteral("child")); parent->clearDiagnostics(); QVERIFY(parent->diagnostics().isEmpty()); } void TestDUChain::testIdentifiers() { QualifiedIdentifier aj(QStringLiteral("::Area::jump")); QCOMPARE(aj.count(), 2); QCOMPARE(aj.explicitlyGlobal(), true); QCOMPARE(aj.at(0), Identifier(QStringLiteral("Area"))); QCOMPARE(aj.at(1), Identifier(QStringLiteral("jump"))); QualifiedIdentifier aj2 = QualifiedIdentifier(QStringLiteral("Area::jump")); QCOMPARE(aj2.count(), 2); QCOMPARE(aj2.explicitlyGlobal(), false); QCOMPARE(aj2.at(0), Identifier(QStringLiteral("Area"))); QCOMPARE(aj2.at(1), Identifier(QStringLiteral("jump"))); QVERIFY(aj != aj2); QVERIFY(QualifiedIdentifier(QString()) == QualifiedIdentifier()); QVERIFY(QualifiedIdentifier(QString()).index() == QualifiedIdentifier().index()); QualifiedIdentifier ajt(QStringLiteral("Area::jump::test")); QualifiedIdentifier jt(QStringLiteral("jump::test")); QualifiedIdentifier ajt2(QStringLiteral("Area::jump::tes")); QualifiedIdentifier t(QStringLiteral(" Area::jump ::tes")); QCOMPARE(t.count(), 3); QCOMPARE(t.at(0).templateIdentifiersCount(), 2u); QCOMPARE(t.at(1).templateIdentifiersCount(), 1u); QCOMPARE(t.at(2).templateIdentifiersCount(), 1u); QCOMPARE(t.at(0).identifier().str(), QStringLiteral("Area")); QCOMPARE(t.at(1).identifier().str(), QStringLiteral("jump")); QCOMPARE(t.at(2).identifier().str(), QStringLiteral("tes")); QualifiedIdentifier op1(QStringLiteral("operator<")); QualifiedIdentifier op2(QStringLiteral("operator<=")); QualifiedIdentifier op3(QStringLiteral("operator>")); QualifiedIdentifier op4(QStringLiteral("operator>=")); QualifiedIdentifier op5(QStringLiteral("operator()")); QualifiedIdentifier op6(QStringLiteral("operator( )")); QCOMPARE(op1.count(), 1); QCOMPARE(op2.count(), 1); QCOMPARE(op3.count(), 1); QCOMPARE(op4.count(), 1); QCOMPARE(op5.count(), 1); QCOMPARE(op6.count(), 1); QCOMPARE(op4.toString(), QStringLiteral("operator>=")); QCOMPARE(op3.toString(), QStringLiteral("operator>")); QCOMPARE(op1.toString(), QStringLiteral("operator<")); QCOMPARE(op2.toString(), QStringLiteral("operator<=")); QCOMPARE(op5.toString(), QStringLiteral("operator()")); QCOMPARE(op6.toString(), QStringLiteral("operator( )")); QCOMPARE(QualifiedIdentifier(QStringLiteral("Area::jump ::tes")).index(), t.index()); QCOMPARE(op4.index(), QualifiedIdentifier(QStringLiteral("operator>=")).index()); QualifiedIdentifier pushTest(QStringLiteral("foo")); QCOMPARE(pushTest.count(), 1); QCOMPARE(pushTest.toString(), QStringLiteral("foo")); pushTest.push(Identifier(QStringLiteral("bar"))); QCOMPARE(pushTest.count(), 2); QCOMPARE(pushTest.toString(), QStringLiteral("foo::bar")); pushTest.push(QualifiedIdentifier(QStringLiteral("baz::asdf"))); QCOMPARE(pushTest.count(), 4); QCOMPARE(pushTest.toString(), QStringLiteral("foo::bar::baz::asdf")); QualifiedIdentifier mergeTest = pushTest.merge(QualifiedIdentifier(QStringLiteral("meh::muh"))); QCOMPARE(mergeTest.count(), 6); QCOMPARE(mergeTest.toString(), QStringLiteral("meh::muh::foo::bar::baz::asdf")); QualifiedIdentifier plusTest = QualifiedIdentifier(QStringLiteral("la::lu")) + QualifiedIdentifier(QStringLiteral("ba::bu")); QCOMPARE(plusTest.count(), 4); QCOMPARE(plusTest.toString(), QStringLiteral("la::lu::ba::bu")); ///@todo create a big randomized test for the identifier repository(check that indices are the same) } #if 0 ///NOTE: the "unit tests" below are not automated, they - so far - require /// human interpretation which is not useful for a unit test! /// someone should investigate what the expected output should be /// and add proper QCOMPARE/QVERIFY checks accordingly ///FIXME: this needs to be rewritten in order to remove dependencies on formerly run unit tests void TestDUChain::testImportCache() { KDevelop::globalItemRepositoryRegistry().printAllStatistics(); KDevelop::RecursiveImportRepository::repository()->printStatistics(); //Analyze the whole existing import-cache //This is very expensive, since it involves loading all existing top-contexts uint topContextCount = DUChain::self()->newTopContextIndex(); uint analyzedCount = 0; uint totalImportCount = 0; uint naiveNodeCount = 0; QSet reachableNodes; DUChainReadLocker lock(DUChain::lock()); for (uint a = 0; a < topContextCount; ++a) { if (a % qMax(1u, topContextCount / 100) == 0) { qDebug() << "progress:" << (a * 100) / topContextCount; } TopDUContext* context = DUChain::self()->chainForIndex(a); if (context) { TopDUContext::IndexedRecursiveImports imports = context->recursiveImportIndices(); ++analyzedCount; totalImportCount += imports.set().count(); collectReachableNodes(reachableNodes, imports.setIndex()); naiveNodeCount += collectNaiveNodeCount(imports.setIndex()); } } QVERIFY(analyzedCount); qDebug() << "average total count of imports:" << totalImportCount / analyzedCount; qDebug() << "count of reachable nodes:" << reachableNodes.size(); qDebug() << "naive node-count:" << naiveNodeCount << "sharing compression factor:" << (( float )reachableNodes.size()) / (( float )naiveNodeCount); } #endif void TestDUChain::benchCodeModel() { const IndexedString file("testFile"); QVERIFY(!QTypeInfo::isStatic); int i = 0; QBENCHMARK { CodeModel::self().addItem(file, QualifiedIdentifier("testQID" + QString::number(i++)), KDevelop::CodeModelItem::Class); } } void TestDUChain::benchTypeRegistry() { IntegralTypeData data; data.m_dataType = IntegralType::TypeInt; data.typeClassId = IntegralType::Identity; data.inRepository = false; data.m_modifiers = 42; data.m_dynamic = false; data.refCount = 1; IntegralTypeData to; QFETCH(int, func); QBENCHMARK { switch (func) { case 0: TypeSystem::self().dataClassSize(data); break; case 1: TypeSystem::self().dynamicSize(data); break; case 2: TypeSystem::self().create(&data); break; case 3: TypeSystem::self().isFactoryLoaded(data); break; case 4: TypeSystem::self().copy(data, to, !data.m_dynamic); break; case 5: TypeSystem::self().copy(data, to, data.m_dynamic); break; case 6: TypeSystem::self().callDestructor(&data); break; } } } void TestDUChain::benchTypeRegistry_data() { QTest::addColumn("func"); QTest::newRow("dataClassSize") << 0; QTest::newRow("dynamicSize") << 1; QTest::newRow("create") << 2; QTest::newRow("isFactoryLoaded") << 3; QTest::newRow("copy") << 4; QTest::newRow("copyNonDynamic") << 5; QTest::newRow("callDestructor") << 6; } void TestDUChain::benchDuchainReadLocker() { QBENCHMARK { DUChainReadLocker lock; } } void TestDUChain::benchDuchainWriteLocker() { QBENCHMARK { DUChainWriteLocker lock; } } void TestDUChain::benchDUChainItemFactory_copy() { DUChainItemFactory factory; DeclarationData from, to; from.classId = Declaration::Identity; QFETCH(int, constant); bool c = constant; QBENCHMARK { factory.copy(from, to, c); if (constant == 2) { c = !c; } } } void TestDUChain::benchDUChainItemFactory_copy_data() { QTest::addColumn("constant"); QTest::newRow("non-const") << 0; QTest::newRow("const") << 1; QTest::newRow("flip") << 2; } void TestDUChain::benchDeclarationQualifiedIdentifier() { QVector contexts; contexts.reserve(10); DUChainWriteLocker lock; contexts << new TopDUContext(IndexedString("/tmp/something"), {0, 0, INT_MAX, INT_MAX}); for (int i = 1; i < contexts.capacity(); ++i) { contexts << new DUContext({0, 0, INT_MAX, INT_MAX}, contexts.at(i - 1)); contexts.last()->setLocalScopeIdentifier(QualifiedIdentifier(QString::number(i))); } auto dec = new Declaration({0, 0, 0, 1}, contexts.last()); dec->setIdentifier(Identifier(QStringLiteral("myDecl"))); qDebug() << "start benchmark!"; qint64 count = 0; QBENCHMARK { count += dec->qualifiedIdentifier().count(); } QVERIFY(count > 0); } #include "test_duchain.moc" #include "moc_test_duchain.cpp" diff --git a/kdevplatform/language/duchain/topducontext.cpp b/kdevplatform/language/duchain/topducontext.cpp index 4422398396..32293feb55 100644 --- a/kdevplatform/language/duchain/topducontext.cpp +++ b/kdevplatform/language/duchain/topducontext.cpp @@ -1,1220 +1,1220 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-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 "topducontext.h" #include "topducontextutils.h" #include #include "persistentsymboltable.h" #include "problem.h" #include "declaration.h" #include "duchain.h" #include "duchainlock.h" #include "parsingenvironment.h" #include "duchainpointer.h" #include "declarationid.h" #include "namespacealiasdeclaration.h" #include "aliasdeclaration.h" #include "uses.h" #include "topducontextdata.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include #include // #define DEBUG_SEARCH const uint maxApplyAliasesRecursion = 100; namespace KDevelop { Utils::BasicSetRepository* RecursiveImportRepository::repository() { static Utils::BasicSetRepository recursiveImportRepositoryObject(QStringLiteral( "Recursive Imports"), &KDevelop::globalItemRepositoryRegistry()); return &recursiveImportRepositoryObject; } ReferencedTopDUContext::ReferencedTopDUContext(TopDUContext* context) : m_topContext(context) { if (m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::ReferencedTopDUContext(const ReferencedTopDUContext& rhs) : m_topContext(rhs.m_topContext) { if (m_topContext) DUChain::self()->refCountUp(m_topContext); } ReferencedTopDUContext::~ReferencedTopDUContext() { if (m_topContext && !DUChain::deleted()) DUChain::self()->refCountDown(m_topContext); } ReferencedTopDUContext& ReferencedTopDUContext::operator=(const ReferencedTopDUContext& rhs) { if (m_topContext == rhs.m_topContext) return *this; if (m_topContext) DUChain::self()->refCountDown(m_topContext); m_topContext = rhs.m_topContext; if (m_topContext) DUChain::self()->refCountUp(m_topContext); return *this; } DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_usedDeclarationIds, DeclarationId) DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_problems, LocalIndexedProblem) REGISTER_DUCHAIN_ITEM(TopDUContext); QMutex importStructureMutex(QMutex::Recursive); //Contains data that is not shared among top-contexts that share their duchain entries class TopDUContextLocalPrivate { public: TopDUContextLocalPrivate (TopDUContext* ctxt, uint index) : m_ctxt(ctxt) , m_ownIndex(index) , m_inDuChain(false) { m_indexedRecursiveImports.insert(index); } ~TopDUContextLocalPrivate() { //Either we use some other contexts data and have no users, or we own the data and have users that share it. QMutexLocker lock(&importStructureMutex); foreach (const DUContext::Import& import, m_importedContexts) if (DUChain::self()->isInMemory(import.topContextIndex()) && dynamic_cast(import.context(nullptr))) dynamic_cast(import.context(nullptr))->m_local->m_directImporters.remove(m_ctxt); } ///@todo Make all this work consistently together with import-caching //After loading, should rebuild the links void rebuildDynamicImportStructure() { //Currently we do not store the whole data in TopDUContextLocalPrivate, so we reconstruct it from what was stored by DUContext. Q_ASSERT(m_importedContexts.isEmpty()); FOREACH_FUNCTION(const DUContext::Import& import, m_ctxt->d_func()->m_importedContexts) { if (DUChain::self()->isInMemory(import.topContextIndex())) { Q_ASSERT(import.context(nullptr)); TopDUContext* top = import.context(nullptr)->topContext(); Q_ASSERT(top); addImportedContextRecursively(top, false, true); } } FOREACH_FUNCTION(const IndexedDUContext &importer, m_ctxt->d_func()->m_importers) { if (DUChain::self()->isInMemory(importer.topContextIndex())) { Q_ASSERT(importer.context()); TopDUContext* top = importer.context()->topContext(); Q_ASSERT(top); top->m_local->addImportedContextRecursively(m_ctxt, false, true); } } } //Index of this top-context within the duchain //Since the data of top-contexts can be shared among multiple, this can be used to add imports that are local to this top-context. QVector m_importedContexts; // mutable bool m_haveImportStructure : 1; TopDUContext* m_ctxt; QSet m_directImporters; ParsingEnvironmentFilePointer m_file; QExplicitlySharedDataPointer m_ast; uint m_ownIndex; bool m_inDuChain; void clearImportedContextsRecursively() { QMutexLocker lock(&importStructureMutex); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); QSet> rebuild; foreach (const DUContext::Import& import, m_importedContexts) { auto* top = dynamic_cast(import.context(nullptr)); if (top) { top->m_local->m_directImporters.remove(m_ctxt); if (!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(top, top, 1, rebuild); QHash> b = top->m_local->m_recursiveImports; for (RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if (m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == top) removeImportedContextRecursion(top, it.key(), it->first + 1, rebuild); //Remove all contexts that are imported through the context } } } } m_importedContexts.clear(); rebuildImportStructureRecursion(rebuild); Q_ASSERT(m_recursiveImports.isEmpty()); // Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1); } //Adds the context to this and all contexts that import this, and manages m_recursiveImports void addImportedContextRecursively(TopDUContext* context, bool temporary, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.insert(m_ctxt); if (local) { // note: m_importedContexts may end up with duplicate entries -- not sure whether we should protect against this --Kevin m_importedContexts << DUContext::Import(context, m_ctxt); } if (!m_ctxt->usingImportsCache()) { addImportedContextRecursion(context, context, 1, temporary); QHash> b = context->m_local->m_recursiveImports; for (RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) addImportedContextRecursion(context, it.key(), (*it).first + 1, temporary); //Add contexts that were imported earlier into the given one } } //Removes the context from this and all contexts that import this, and manages m_recursiveImports void removeImportedContextRecursively(TopDUContext* context, bool local) { QMutexLocker lock(&importStructureMutex); context->m_local->m_directImporters.remove(m_ctxt); if (local) m_importedContexts.removeAll(DUContext::Import(context, m_ctxt)); QSet> rebuild; if (!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash> b = context->m_local->m_recursiveImports; for (RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if (m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first + 1, rebuild); //Remove all contexts that are imported through the context } } rebuildImportStructureRecursion(rebuild); } void removeImportedContextsRecursively(const QList& contexts, bool local) { QMutexLocker lock(&importStructureMutex); QSet> rebuild; for (TopDUContext* context : contexts) { context->m_local->m_directImporters.remove(m_ctxt); if (local) m_importedContexts.removeAll(DUContext::Import(context, m_ctxt)); if (!m_ctxt->usingImportsCache()) { removeImportedContextRecursion(context, context, 1, rebuild); QHash> b = context->m_local->m_recursiveImports; for (RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) { if (m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context) removeImportedContextRecursion(context, it.key(), it->first + 1, rebuild); //Remove all contexts that are imported through the context } } } rebuildImportStructureRecursion(rebuild); } //Has an entry for every single recursively imported file, that contains the shortest path, and the next context on that path to the imported context. //This does not need to be stored to disk, because it is defined implicitly. //What makes this most complicated is the fact that loops are allowed in the import structure. - typedef QHash> RecursiveImports; + using RecursiveImports = QHash>; mutable RecursiveImports m_recursiveImports; mutable TopDUContext::IndexedRecursiveImports m_indexedRecursiveImports; private: void addImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int depth, bool temporary = false) { if (m_ctxt->usingImportsCache()) return; // if(!m_haveImportStructure) // return; if (imported == m_ctxt) return; const bool computeShortestPaths = false; ///@todo We do not compute the shortest path. Think what's right. // traceNext->m_local->needImportStructure(); // imported->m_local->needImportStructure(); RecursiveImports::iterator it = m_recursiveImports.find(imported); if (it == m_recursiveImports.end()) { //Insert new path to "imported" m_recursiveImports[imported] = qMakePair(depth, traceNext); m_indexedRecursiveImports.insert(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()+1); Q_ASSERT(traceNext != m_ctxt); } else { if (!computeShortestPaths) return; if (temporary) //For temporary imports, we don't record the best path. return; //It would be better if we would use the following code, but it creates too much cost in updateImportedContextRecursion when imports are removed again. //Check whether the new way to "imported" is shorter than the stored one if ((*it).first > depth) { //Add a shorter path (*it).first = depth; Q_ASSERT(traceNext); (*it).second = traceNext; Q_ASSERT(traceNext == imported || (traceNext->m_local->m_recursiveImports.contains(imported) && traceNext->m_local->m_recursiveImports[imported].first < (*it).first)); } else { //The imported context is already imported through a same/better path, so we can just stop processing. This saves us from endless recursion. return; } } if (temporary) return; for (QSet::const_iterator it = m_directImporters.constBegin(); it != m_directImporters.constEnd(); ++it) { auto* top = dynamic_cast(const_cast(*it)); //Avoid detaching, so use const_cast if (top) ///@todo also record this for local imports top->m_local->addImportedContextRecursion(m_ctxt, imported, depth + 1); } } void removeImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int distance, QSet>& rebuild) { if (m_ctxt->usingImportsCache()) return; if (imported == m_ctxt) return; // if(!m_haveImportStructure) // return; RecursiveImports::iterator it = m_recursiveImports.find(imported); if (it == m_recursiveImports.end()) { //We don't import. Just return, this saves us from endless recursion. return; } else { //Check whether we have imported "imported" through "traceNext". If not, return. Else find a new trace. if ((*it).second == traceNext && (*it).first == distance) { //We need to remove the import through traceNext. Check whether there is another imported context that imports it. m_recursiveImports.erase(it); //In order to prevent problems, we completely remove everything, and re-add it. //Just updating these complex structures is very hard. Q_ASSERT(imported != m_ctxt); m_indexedRecursiveImports.remove(imported->indexed()); // Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()); rebuild.insert(qMakePair(m_ctxt, imported)); //We MUST do this before finding another trace, because else we would create loops for (QSet::const_iterator childIt = m_directImporters.constBegin(); childIt != m_directImporters.constEnd(); ++childIt) { auto* top = dynamic_cast(const_cast(*childIt)); //Avoid detaching, so use const iterator if (top) top->m_local->removeImportedContextRecursion(m_ctxt, imported, distance + 1, rebuild); //Don't use 'it' from here on, it may be invalid } } } } //Updates the trace to 'imported' void rebuildStructure(const TopDUContext* imported); void rebuildImportStructureRecursion(const QSet>& rebuild) { for (QSet>::const_iterator it = rebuild.constBegin(); it != rebuild.constEnd(); ++it) { //for(int a = rebuild.size()-1; a >= 0; --a) { //Find the best imported parent it->first->m_local->rebuildStructure(it->second); } } }; const TopDUContext::IndexedRecursiveImports& TopDUContext::recursiveImportIndices() const { // No lock-check for performance reasons QMutexLocker lock(&importStructureMutex); if (!d_func()->m_importsCache.isEmpty()) return d_func()->m_importsCache; return m_local->m_indexedRecursiveImports; } void TopDUContextData::updateImportCacheRecursion(uint baseIndex, IndexedTopDUContext currentContext, TopDUContext::IndexedRecursiveImports& visited) { if (visited.contains(currentContext.index())) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if (!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); if (currentData->m_importsCache.contains(baseIndex) || currentData->m_importsCache.isEmpty()) { //If we have a loop or no imports-cache is used, we have to look at each import separately. const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for (uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if (next.isValid()) updateImportCacheRecursion(baseIndex, next, visited); } } else { //If we don't have a loop with baseIndex, we can safely just merge with the imported importscache visited += currentData->m_importsCache; } } void TopDUContextData::updateImportCacheRecursion(IndexedTopDUContext currentContext, std::set& visited) { if (visited.find(currentContext.index()) != visited.end()) return; Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called if (!currentContext.data()) { qCDebug(LANGUAGE) << "importing invalid context"; return; } visited.insert(currentContext.index()); const TopDUContextData* currentData = currentContext.data()->topContext()->d_func(); const KDevelop::DUContext::Import* imports = currentData->m_importedContexts(); uint importsSize = currentData->m_importedContextsSize(); for (uint a = 0; a < importsSize; ++a) { IndexedTopDUContext next(imports[a].topContextIndex()); if (next.isValid()) updateImportCacheRecursion(next, visited); } } void TopDUContext::updateImportsCache() { QMutexLocker lock(&importStructureMutex); const bool use_fully_recursive_import_cache_computation = false; if (use_fully_recursive_import_cache_computation) { std::set visited; TopDUContextData::updateImportCacheRecursion(this, visited); Q_ASSERT(visited.find(ownIndex()) != visited.end()); d_func_dynamic()->m_importsCache = IndexedRecursiveImports(visited); } else { d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); TopDUContextData::updateImportCacheRecursion(ownIndex(), this, d_func_dynamic()->m_importsCache); } Q_ASSERT(d_func_dynamic()->m_importsCache.contains(IndexedTopDUContext(this))); Q_ASSERT(usingImportsCache()); Q_ASSERT(imports(this, CursorInRevision::invalid())); if (parsingEnvironmentFile()) parsingEnvironmentFile()->setImportsCache(d_func()->m_importsCache); } bool TopDUContext::usingImportsCache() const { return !d_func()->m_importsCache.isEmpty(); } CursorInRevision TopDUContext::importPosition(const DUContext* target) const { ENSURE_CAN_READ DUCHAIN_D(DUContext); Import import(const_cast(target), const_cast(this), CursorInRevision::invalid()); for (unsigned int a = 0; a < d->m_importedContextsSize(); ++a) if (d->m_importedContexts()[a] == import) return d->m_importedContexts()[a].position; return DUContext::importPosition(target); } void TopDUContextLocalPrivate::rebuildStructure(const TopDUContext* imported) { if (m_ctxt == imported) return; for (QVector::const_iterator parentIt = m_importedContexts.constBegin(); parentIt != m_importedContexts.constEnd(); ++parentIt) { auto* top = dynamic_cast(const_cast(parentIt->context(nullptr))); //To avoid detaching, use const iterator if (top) { // top->m_local->needImportStructure(); if (top == imported) { addImportedContextRecursion(top, imported, 1); } else { RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if (it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } for (unsigned int a = 0; a < m_ctxt->d_func()->m_importedContextsSize(); ++a) { auto* top = dynamic_cast(const_cast(m_ctxt->d_func()->m_importedContexts()[a].context(nullptr))); //To avoid detaching, use const iterator if (top) { // top->m_local->needImportStructure(); if (top == imported) { addImportedContextRecursion(top, imported, 1); } else { RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported); if (it2 != top->m_local->m_recursiveImports.constEnd()) { addImportedContextRecursion(top, imported, (*it2).first + 1); } } } } } void TopDUContext::rebuildDynamicImportStructure() { m_local->rebuildDynamicImportStructure(); } void TopDUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_ASSERT(parent == nullptr && ownIndex != 0); m_local->m_ownIndex = ownIndex; DUContext::rebuildDynamicData(parent, 0); } IndexedTopDUContext TopDUContext::indexed() const { return IndexedTopDUContext(m_local->m_ownIndex); } uint TopDUContext::ownIndex() const { return m_local->m_ownIndex; } TopDUContext::TopDUContext(TopDUContextData& data) : DUContext(data) , m_local(new TopDUContextLocalPrivate(this, data.m_ownIndex)) , m_dynamicData(new TopDUContextDynamicData(this)) { } TopDUContext::TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file) : DUContext(*new TopDUContextData(url), range) , m_local(new TopDUContextLocalPrivate(this, DUChain::newTopContextIndex())) , m_dynamicData(new TopDUContextDynamicData(this)) { Q_ASSERT(url.toUrl().isValid() && !url.toUrl().isRelative()); d_func_dynamic()->setClassId(this); setType(Global); DUCHAIN_D_DYNAMIC(TopDUContext); d->m_features = VisibleDeclarationsAndContexts; d->m_ownIndex = m_local->m_ownIndex; setParsingEnvironmentFile(file); setInSymbolTable(true); } QExplicitlySharedDataPointer TopDUContext::parsingEnvironmentFile() const { return m_local->m_file; } TopDUContext::~TopDUContext() { m_dynamicData->m_deleting = true; //Clear the AST, so that the 'feature satisfaction' cache is eventually updated clearAst(); if (!isOnDisk()) { //Clear the 'feature satisfaction' cache which is managed in ParsingEnvironmentFile setFeatures(Empty); clearUsedDeclarationIndices(); } deleteChildContextsRecursively(); deleteLocalDeclarations(); m_dynamicData->clear(); } void TopDUContext::deleteSelf() { //We've got to make sure that m_dynamicData and m_local are still valid while all the sub-contexts are destroyed TopDUContextLocalPrivate* local = m_local; TopDUContextDynamicData* dynamicData = m_dynamicData; m_dynamicData->m_deleting = true; delete this; delete local; delete dynamicData; } TopDUContext::Features TopDUContext::features() const { uint ret = d_func()->m_features; if (ast()) ret |= TopDUContext::AST; return ( TopDUContext::Features )ret; } void TopDUContext::setFeatures(Features features) { features = ( TopDUContext::Features )(features & (~Recursive)); //Remove the "Recursive" flag since that's only for searching features = ( TopDUContext::Features )(features & (~ForceUpdateRecursive)); //Remove the update flags features = ( TopDUContext::Features )(features & (~AST)); //Remove the AST flag, it's only used while updating d_func_dynamic()->m_features = features; //Replicate features to ParsingEnvironmentFile if (parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(this->features()); } void TopDUContext::setAst(const QExplicitlySharedDataPointer& ast) { ENSURE_CAN_WRITE m_local->m_ast = ast; if (parsingEnvironmentFile()) parsingEnvironmentFile()->setFeatures(features()); } void TopDUContext::setParsingEnvironmentFile(ParsingEnvironmentFile* file) { if (m_local->m_file) //Clear the "feature satisfaction" cache m_local->m_file->setFeatures(Empty); //We do not enforce a duchain lock here, since this is also used while loading a top-context m_local->m_file = QExplicitlySharedDataPointer(file); //Replicate features to ParsingEnvironmentFile if (file) { file->setTopContext(IndexedTopDUContext(ownIndex())); Q_ASSERT(file->indexedTopContext().isValid()); file->setFeatures(d_func()->m_features); file->setImportsCache(d_func()->m_importsCache); } } struct TopDUContext::FindDeclarationsAcceptor { FindDeclarationsAcceptor(const TopDUContext* _top, DeclarationList& _target, const DeclarationChecker& _check, SearchFlags _flags) : top(_top) , target(_target) , check(_check) { flags = _flags; } bool operator()(const QualifiedIdentifier& id) { #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "accepting" << id.toString(); #endif PersistentSymbolTable::Declarations allDecls; //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter; //This is used if filtering is disabled PersistentSymbolTable::Declarations::Iterator unchecked; if (check.flags & DUContext::NoImportsCheck) { allDecls = PersistentSymbolTable::self().declarations(id); unchecked = allDecls.iterator(); } else filter = PersistentSymbolTable::self().filteredDeclarations(id, top->recursiveImportIndices()); while (filter || unchecked) { IndexedDeclaration iDecl; if (filter) { iDecl = *filter; ++filter; } else { iDecl = *unchecked; ++unchecked; } Declaration* decl = iDecl.data(); if (!decl) continue; if (!check(decl)) continue; if (!(flags & DontResolveAliases) && decl->kind() == Declaration::Alias) { //Apply alias declarations auto* alias = static_cast(decl); if (alias->aliasedDeclaration().isValid()) { decl = alias->aliasedDeclaration().declaration(); } else { qCDebug(LANGUAGE) << "lost aliased declaration"; } } target.append(decl); } check.createVisibleCache = nullptr; return !top->foundEnough(target, flags); } const TopDUContext* top; DeclarationList& target; const DeclarationChecker& check; QFlags flags; }; bool TopDUContext::findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags, uint /*depth*/) const { ENSURE_CAN_READ #ifdef DEBUG_SEARCH for (const SearchItem::Ptr& idTree : identifiers) foreach (const QualifiedIdentifier &id, idTree->toList()) qCDebug(LANGUAGE) << "searching item" << id.toString(); #endif DeclarationChecker check(this, position, dataType, flags); FindDeclarationsAcceptor storer(this, ret, check, flags); ///The actual scopes are found within applyAliases, and each complete qualified identifier is given to FindDeclarationsAcceptor. ///That stores the found declaration to the output. applyAliases(identifiers, storer, position, false); return true; } //This is used to prevent endless recursion due to "using namespace .." declarations, by storing all imports that are already being used. struct TopDUContext::ApplyAliasesBuddyInfo { ApplyAliasesBuddyInfo(uint importChainType, ApplyAliasesBuddyInfo* predecessor, const IndexedQualifiedIdentifier& importId) : m_importChainType(importChainType) , m_predecessor(predecessor) , m_importId(importId) { if (m_predecessor && m_predecessor->m_importChainType != importChainType) m_predecessor = nullptr; } bool alreadyImporting(const IndexedQualifiedIdentifier& id) { ApplyAliasesBuddyInfo* current = this; while (current) { if (current->m_importId == id) return true; current = current->m_predecessor; } return false; } uint m_importChainType; ApplyAliasesBuddyInfo* m_predecessor; IndexedQualifiedIdentifier m_importId; }; ///@todo Implement a cache so at least the global import checks don't need to be done repeatedly. The cache should be thread-local, using DUChainPointer for the hashed items, and when an item was deleted, it should be discarded template bool TopDUContext::applyAliases(const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth) const { if (recursionDepth > maxApplyAliasesRecursion) { const auto searches = identifier->toList(); QualifiedIdentifier id; if (!searches.isEmpty()) id = searches.first(); qCDebug(LANGUAGE) << "maximum apply-aliases recursion reached while searching" << id; } bool foundAlias = false; QualifiedIdentifier id(previous); id.push(identifier->identifier); if (!id.inRepository()) return true; //If the qualified identifier is not in the identifier repository, it cannot be registered anywhere, so there's nothing we need to do if (!identifier->next.isEmpty() || canBeNamespace) { //If it cannot be a namespace, the last part of the scope will be ignored //Search for namespace-aliases, by using globalAliasIdentifier, which is inserted into the symbol-table by NamespaceAliasDeclaration QualifiedIdentifier aliasId(id); aliasId.push(globalIndexedAliasIdentifier()); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "checking" << id.toString(); #endif if (aliasId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().filteredDeclarations(aliasId, recursiveImportIndices()); if (filter) { DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, nullptr); //The first part of the identifier has been found as a namespace-alias. //In c++, we only need the first alias. However, just to be correct, follow them all for now. for (; filter; ++filter) { Declaration* aliasDecl = filter->data(); if (!aliasDecl) continue; if (!check(aliasDecl)) continue; if (aliasDecl->kind() != Declaration::NamespaceAlias) continue; if (foundAlias) break; Q_ASSERT(dynamic_cast(aliasDecl)); auto* alias = static_cast(aliasDecl); foundAlias = true; QualifiedIdentifier importIdentifier = alias->importIdentifier(); if (importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if (buddy && buddy->alreadyImporting(importIdentifier)) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(1, buddy, importIdentifier); if (identifier->next.isEmpty()) { //Just insert the aliased namespace identifier if (!accept(importIdentifier)) return false; } else { //Create an identifiers where namespace-alias part is replaced with the alias target foreach (const SearchItem::Ptr& item, identifier->next) if (!applyAliases(importIdentifier, item, accept, position, canBeNamespace, &info, recursionDepth + 1)) return false; } } } } } if (!foundAlias) { //If we haven't found an alias, put the current versions into the result list. Additionally we will compute the identifiers transformed through "using". if (identifier->next.isEmpty()) { if (!accept(id)) //We're at the end of a qualified identifier, accept it return false; } else { foreach (const SearchItem::Ptr& next, identifier->next) if (!applyAliases(id, next, accept, position, canBeNamespace, nullptr, recursionDepth + 1)) return false; } } /*if( !prefix.explicitlyGlobal() || !prefix.isEmpty() ) {*/ ///@todo check iso c++ if using-directives should be respected on top-level when explicitly global ///@todo this is bad for a very big repository(the chains should be walked for the top-context instead) //Find all namespace-imports at given scope { QualifiedIdentifier importId(previous); importId.push(globalIndexedImportIdentifier()); #ifdef DEBUG_SEARCH // qCDebug(LANGUAGE) << "checking imports in" << (backPointer ? id.toString() : QStringLiteral("global")); #endif if (importId.inRepository()) { //This iterator efficiently filters the visible declarations out of all declarations PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().filteredDeclarations(importId, recursiveImportIndices()); if (filter) { DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, nullptr); for (; filter; ++filter) { Declaration* importDecl = filter->data(); if (!importDecl) continue; //We must never break or return from this loop, because else we might be creating a bad cache if (!check(importDecl)) continue; //Search for the identifier with the import-identifier prepended Q_ASSERT(dynamic_cast(importDecl)); auto* alias = static_cast(importDecl); #ifdef DEBUG_SEARCH qCDebug(LANGUAGE) << "found import of" << alias->importIdentifier().toString(); #endif QualifiedIdentifier importIdentifier = alias->importIdentifier(); if (importIdentifier.isEmpty()) { qCDebug(LANGUAGE) << "found empty import"; continue; } if (buddy && buddy->alreadyImporting(importIdentifier)) continue; //This import has already been applied to this search ApplyAliasesBuddyInfo info(2, buddy, importIdentifier); if (previous != importIdentifier) if (!applyAliases(importIdentifier, identifier, accept, importDecl->topContext() == this ? importDecl->range().start : position, canBeNamespace, &info, recursionDepth + 1)) return false; } } } } return true; } template void TopDUContext::applyAliases(const SearchItem::PtrList& identifiers, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace) const { QualifiedIdentifier emptyId; for (const SearchItem::Ptr& item : identifiers) applyAliases(emptyId, item, acceptor, position, canBeNamespace, nullptr, 0); } TopDUContext* TopDUContext::topContext() const { return const_cast(this); } bool TopDUContext::deleting() const { return m_dynamicData->m_deleting; } QList TopDUContext::problems() const { ENSURE_CAN_READ const auto data = d_func(); QList ret; ret.reserve(data->m_problemsSize()); for (uint i = 0; i < data->m_problemsSize(); ++i) { ret << ProblemPointer(data->m_problems()[i].data(this)); } return ret; } void TopDUContext::setProblems(const QList& problems) { ENSURE_CAN_WRITE clearProblems(); for (const auto& problem : problems) { addProblem(problem); } } void TopDUContext::addProblem(const ProblemPointer& problem) { ENSURE_CAN_WRITE Q_ASSERT(problem); auto data = d_func_dynamic(); // store for indexing LocalIndexedProblem indexedProblem(problem, this); Q_ASSERT(indexedProblem.isValid()); data->m_problemsList().append(indexedProblem); Q_ASSERT(indexedProblem.data(this)); } void TopDUContext::clearProblems() { ENSURE_CAN_WRITE d_func_dynamic()->m_problemsList().clear(); m_dynamicData->clearProblems(); } QVector TopDUContext::importers() const { ENSURE_CAN_READ return QVector::fromList(m_local->m_directImporters.toList()); } QList TopDUContext::loadedImporters() const { ENSURE_CAN_READ return m_local->m_directImporters.toList(); } QVector TopDUContext::importedParentContexts() const { ENSURE_CAN_READ return DUContext::importedParentContexts(); } bool TopDUContext::imports(const DUContext* origin, const CursorInRevision& position) const { return importsPrivate(origin, position); } bool TopDUContext::importsPrivate(const DUContext* origin, const CursorInRevision& position) const { Q_UNUSED(position); if (const auto* top = dynamic_cast(origin)) { QMutexLocker lock(&importStructureMutex); bool ret = recursiveImportIndices().contains(IndexedTopDUContext(const_cast(top))); if (top == this) Q_ASSERT(ret); return ret; } else { //Cannot import a non top-context return false; } } void TopDUContext::clearImportedParentContexts() { if (usingImportsCache()) { d_func_dynamic()->m_importsCache = IndexedRecursiveImports(); d_func_dynamic()->m_importsCache.insert(IndexedTopDUContext(this)); } DUContext::clearImportedParentContexts(); m_local->clearImportedContextsRecursively(); Q_ASSERT(m_local->m_recursiveImports.count() == 0); Q_ASSERT(m_local->m_indexedRecursiveImports.count() == 1); Q_ASSERT(imports(this, CursorInRevision::invalid())); } void TopDUContext::addImportedParentContext(DUContext* context, const CursorInRevision& position, bool anonymous, bool temporary) { if (context == this) return; if (!dynamic_cast(context)) { //We cannot do this, because of the extended way we treat top-context imports. qCDebug(LANGUAGE) << "tried to import a non top-context into a top-context. This is not possible."; return; } //Always make the contexts anonymous, because we care about importers in TopDUContextLocalPrivate DUContext::addImportedParentContext(context, position, anonymous, temporary); m_local->addImportedContextRecursively(static_cast(context), temporary, true); } void TopDUContext::removeImportedParentContext(DUContext* context) { DUContext::removeImportedParentContext(context); m_local->removeImportedContextRecursively(static_cast(context), true); } void TopDUContext::addImportedParentContexts(const QVector>& contexts, bool temporary) { - typedef QPair Pair; + using Pair = QPair; for (const Pair pair : contexts) { addImportedParentContext(pair.first, pair.second, false, temporary); } } void TopDUContext::removeImportedParentContexts(const QList& contexts) { for (TopDUContext* context : contexts) { DUContext::removeImportedParentContext(context); } m_local->removeImportedContextsRecursively(contexts, true); } /// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.) bool TopDUContext::inDUChain() const { return m_local->m_inDuChain; } /// This flag is only used by DUChain, never change it from outside. void TopDUContext::setInDuChain(bool b) { m_local->m_inDuChain = b; } bool TopDUContext::isOnDisk() const { ///@todo Change this to releasingToDisk, and only enable it while saving a top-context to disk. return m_dynamicData->isOnDisk(); } void TopDUContext::clearUsedDeclarationIndices() { ENSURE_CAN_WRITE for (unsigned int a = 0; a < d_func()->m_usedDeclarationIdsSize(); ++a) DUChain::uses()->removeUse(d_func()->m_usedDeclarationIds()[a], this); d_func_dynamic()->m_usedDeclarationIdsList().clear(); } void TopDUContext::deleteUsesRecursively() { clearUsedDeclarationIndices(); KDevelop::DUContext::deleteUsesRecursively(); } Declaration* TopDUContext::usedDeclarationForIndex(unsigned int declarationIndex) const { ENSURE_CAN_READ if (declarationIndex & (1 << 31)) { //We use the highest bit to mark direct indices into the local declarations declarationIndex &= ~(1 << 31); //unset the highest bit return m_dynamicData->declarationForIndex(declarationIndex); } else if (declarationIndex < d_func()->m_usedDeclarationIdsSize()) return d_func()->m_usedDeclarationIds()[declarationIndex].declaration(this); else return nullptr; } int TopDUContext::indexForUsedDeclaration(Declaration* declaration, bool create) { if (create) { ENSURE_CAN_WRITE } else { ENSURE_CAN_READ } if (!declaration) { return std::numeric_limits::max(); } if (declaration->topContext() == this && !declaration->inSymbolTable() && !m_dynamicData->isTemporaryDeclarationIndex(declaration->ownIndex())) { uint index = declaration->ownIndex(); Q_ASSERT(!(index & (1 << 31))); return ( int )(index | (1 << 31)); //We don't put context-local declarations into the list, that's a waste. We just use the mark them with the highest bit. } // if the declaration can not be found from this top-context, we create a direct // reference by index, to ensure that the use can be resolved in // usedDeclarationForIndex bool useDirectId = !recursiveImportIndices().contains(declaration->topContext()); DeclarationId id(declaration->id(useDirectId)); int index = -1; uint size = d_func()->m_usedDeclarationIdsSize(); const DeclarationId* ids = d_func()->m_usedDeclarationIds(); ///@todo Make m_usedDeclarationIds sorted, and find the decl. using binary search for (unsigned int a = 0; a < size; ++a) if (ids[a] == id) { index = a; break; } if (index != -1) return index; if (!create) return std::numeric_limits::max(); d_func_dynamic()->m_usedDeclarationIdsList().append(id); if (declaration->topContext() != this) DUChain::uses()->addUse(id, this); return d_func()->m_usedDeclarationIdsSize() - 1; } QVector allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges) { QVector ret; int declarationIndex = context->indexForUsedDeclaration(declaration, false); if (declarationIndex == std::numeric_limits::max()) return ret; return allUses(context, declarationIndex, noEmptyRanges); } QExplicitlySharedDataPointer TopDUContext::ast() const { return m_local->m_ast; } void TopDUContext::clearAst() { setAst(QExplicitlySharedDataPointer(nullptr)); } IndexedString TopDUContext::url() const { return d_func()->m_url; } } diff --git a/kdevplatform/language/duchain/topducontext.h b/kdevplatform/language/duchain/topducontext.h index 8b0664100f..195e8a9049 100644 --- a/kdevplatform/language/duchain/topducontext.h +++ b/kdevplatform/language/duchain/topducontext.h @@ -1,396 +1,396 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-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 KDEVPLATFORM_TOPDUCONTEXT_H #define KDEVPLATFORM_TOPDUCONTEXT_H #include "ducontext.h" #include #include template class QExplicitlySharedDataPointer; namespace KDevelop { class IAstContainer; class QualifiedIdentifier; class DUChain; class ParsingEnvironmentFile; class TopDUContextData; class TopDUContextLocalPrivate; class IndexedTopDUContext; // class TopDUContextDynamicData; class Problem; class DeclarationChecker; class TopDUContext; struct KDEVPLATFORMLANGUAGE_EXPORT RecursiveImportRepository { static Utils::BasicSetRepository* repository(); }; ///Maps an imported top-context to a pair: ///1. The distance to the top-context, and 2. The next step towards the top-context ///in the chain. -typedef QHash> RecursiveImports; +using RecursiveImports = QHash>; -typedef DUChainPointer TopDUContextPointer; +using TopDUContextPointer = DUChainPointer; -typedef QExplicitlySharedDataPointer ProblemPointer; +using ProblemPointer = QExplicitlySharedDataPointer; ///KDevelop can unload unused top-context at any time. To prevent unloading, ///keep a ReferencedTopDUContext. class KDEVPLATFORMLANGUAGE_EXPORT ReferencedTopDUContext { public: ReferencedTopDUContext(TopDUContext* context = nullptr); ReferencedTopDUContext(const ReferencedTopDUContext& rhs); ~ReferencedTopDUContext(); ReferencedTopDUContext& operator=(const ReferencedTopDUContext& rhs); inline TopDUContext* data() const { return m_topContext; } inline operator TopDUContext*() const { return m_topContext; } inline bool operator==(const ReferencedTopDUContext& rhs) const { return m_topContext == rhs.m_topContext; } inline bool operator!=(const ReferencedTopDUContext& rhs) const { return m_topContext != rhs.m_topContext; } inline TopDUContext* operator->() const { return m_topContext; } inline uint hash() const { return ( uint )((( quint64 )m_topContext) * 37); } private: TopDUContext* m_topContext; }; /** * The top context in a definition-use chain for one source file. * * Implements SymbolTable lookups and locking for the chain. * * Contexts and Classes can only be found through TopDUContext if they are in the symbol table. * @see DUContext::setInSymbolTable, Declaration::setInSymbolTable * * \todo move the registration with DUChain here * * @warning Do not delete top-contexts directly, use DUChain::removeDocumentChain instead. */ class KDEVPLATFORMLANGUAGE_EXPORT TopDUContext : public DUContext { public: explicit TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file = nullptr); explicit TopDUContext(TopDUContextData& data); TopDUContext* topContext() const override; ///Returns an indexed representation of this top-context. Indexed representations stay valid even if the top-context is unloaded. IndexedTopDUContext indexed() const; uint ownIndex() const; IndexedString url() const override; /** * @see ParsingEnvironmentFile * May return zero if no file was set. * */ QExplicitlySharedDataPointer parsingEnvironmentFile() const; /// Returns true if this object is being deleted, otherwise false. bool deleting() const; /// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.) can be changed bool inDUChain() const override; /// This flag is only used by DUChain, never change it from outside. void setInDuChain(bool); /// Whether this top-context has a stored version on disk bool isOnDisk() const; /** * Returns a list of all problems encountered while parsing this top-context. * Does not include the problems of imported contexts. * */ QList problems() const; /** * Add a parsing-problem to this context. * * \note you must be holding a write lock when you access this function. * */ void addProblem(const ProblemPointer& problem); /** * Clear the list of problems * * \note you must be holding a write lock when you access this function. */ void clearProblems(); /** * Set the list of problems, replacing all existing ones. * * \note you must be holding a write lock when you access this function. */ void setProblems(const QList& pointers); /** * Determine if this chain imports another chain recursively. * * This uses the imports-cache for speedup if it is available, thus it is not necessarily 100% correct * if the cache is not up-to-date. * * \note you must be holding a read but not a write chain lock when you access this function. */ bool imports(const DUContext* origin, const CursorInRevision& position) const override; enum { Identity = 4 }; enum Features : quint16 { ///Top-context features standard that can be requested from the duchain, and that are stored in the features() member. Empty = 0, //Only the top-context structure (imports etc.) is built, but no declarations and no contexts SimplifiedVisibleDeclarationsAndContexts = 2, //The top-context should only contain publically simplified accessible declarations and contexts, without doing type look-up, //without extended information like function-argument declarations, etc., imported contexts can be parsed with 'Empty' features //This flag essentially leads to a ctags-like processing level. VisibleDeclarationsAndContexts = SimplifiedVisibleDeclarationsAndContexts + 4, //Default: The top-context should only contain publically accessible declarations and contexts AllDeclarationsAndContexts = VisibleDeclarationsAndContexts + 8, //The top-context should also contain non-public declarations and contexts, but no uses AllDeclarationsContextsAndUses = 16 + AllDeclarationsAndContexts, //The top-context should contain uses and all declarations + contexts AST = 32, //Signalizes that the ast() should be filled AllDeclarationsContextsUsesAndAST = AST | AllDeclarationsContextsAndUses, //Convenience flag, combining AST and AllDeclarationsContextsAndUses ///Additional update-flags that have a special meaning during updating, but are not set stored into a top-context Recursive = 64, //Request the given features on all recursively imported contexts. Only the features are applied recursively (including AST) ForceUpdate = 128, //Enforce updating the top-context ForceUpdateRecursive = ForceUpdate | 256, //Enforce updating the top-context and all its imports ///You can define own language-dependent features behind this flag LastFeature = 512 }; ///Returns the currently active features of this top-context. The features will include AST if ast() is valid. Features features() const; ///Set the features of this top-context. These features are ignored: AST, ForceUpdate, and ForceUpdateRecursive. void setFeatures(Features); /** * Retrieves or creates a local index that is to be used for referencing the given @param declaration * in local uses. Also registers this context as a user of the declaration. * @param create If this is false, only already registered indices will be returned. * If the declaration is not registered, std::numeric_limits::max() is returned * * The duchain must be write-locked if create is true, else it must at least be read-locked. * */ int indexForUsedDeclaration(Declaration* declaration, bool create = true); /** * Tries to retrieve the used declaration * @param declarationIndex The index of the declaration which have to be retrieved * */ Declaration* usedDeclarationForIndex(unsigned int declarationIndex) const; /** * You can use this before you rebuild all uses. This does not affect any uses directly, * it only invalidates the mapping of declarationIndices to Declarations. * * usedDeclarationForIndex(..) must not be called until the use has gotten a new index through * indexForUsedDeclaration(..). * */ void clearUsedDeclarationIndices(); /** * Recursively deletes all contained uses, declaration-indices, etc. */ void deleteUsesRecursively() override; /** * Returns the AST Container, that contains the AST created during parsing. * This is only created if you request the AST feature for parsing. * It may be discarded at any time. Every update without the AST feature will discard it. * The actual contents is language-specific. * * @todo Figure out logic to get rid of AST when it is not needed/useful */ QExplicitlySharedDataPointer ast() const; /** * Sets the AST Container. */ void setAst(const QExplicitlySharedDataPointer& ast); /** * Utility function to clear the AST Container */ void clearAst(); ///@param temporary If this is true, importers of this context will not be notified of the new imports. This greatly increases performance while removing the context, ///but creates in inconsistent import-structure. Therefore it is only suitable for temporary imports. These imports will not be visible from contexts that import this one. ///When this top-context does not own its private data, the import is added locally only to this context, not into the shared data. void addImportedParentContext(DUContext* context, const CursorInRevision& position = CursorInRevision(), bool anonymous = false, bool temporary = false) override; ///Use this for mass-adding of imported contexts, it is faster than adding them individually. ///@param temporary If this is true, importers of this context will not be notified of the new imports. This greatly increases performance while removing the context, ///but creates in inconsistent import-structure. Therefore it is only suitable for temporary imports. These imports will not be visible from contexts that import this one. ///When this top-context does not own its private data, the import is added locally only to this context, not into the shared data. virtual void addImportedParentContexts(const QVector>& contexts, bool temporary = false); ///When this top-context does not own its private data, the import is removed locally only from this context, not from the shared data. void removeImportedParentContext(DUContext* context) override; ///Use this for mass-removing of imported contexts, it is faster than removing them individually. ///When this top-context does not own its private data, the import is removed locally only from this context, not from the shared data. virtual void removeImportedParentContexts(const QList& contexts); ///When this top-context does not own its private data, only the local imports of this context are removed, not those from the shared data. void clearImportedParentContexts() override; - typedef Utils::StorableSet IndexedRecursiveImports; + using IndexedRecursiveImports = Utils::StorableSet; QVector importedParentContexts() const override; QVector importers() const override; ///Returns all currently loade importers virtual QList loadedImporters() const; CursorInRevision importPosition(const DUContext* target) const override; ///Returns the set of all recursively imported top-contexts. If import-caching is used, this returns the cached set. ///The list also contains this context itself. This set is used to determine declaration-visibility from within this top-context. const IndexedRecursiveImports& recursiveImportIndices() const; /** * Updates the cache of recursive imports. When you call this, from that moment on the set returned by recursiveImportIndices() is fixed, until * you call it again to update them. If your language has a very complex often-changing import-structure, * like for example in the case of C++, it is recommended to call this during while parsing, instead of using * the expensive builtin implicit mechanism. * Note that if you use caching, you _must_ call this before you see any visibility-effect after adding imports. * * Using import-caching has another big advantage: A top-context can be loaded without loading all its imports. * * Note: This is relatively expensive since it requires loading all imported contexts. * * When this is called, the top-context must already be registered in the duchain. */ void updateImportsCache(); bool usingImportsCache() const; bool findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* source, SearchFlags flags, uint depth) const override; protected: void setParsingEnvironmentFile(ParsingEnvironmentFile*); /** * Does the same as DUContext::updateAliases, except that it uses the symbol-store, and processes the whole identifier. * @param canBeNamespace whether the searched identifier may be a namespace. * If this is true, namespace-aliasing is applied to the last elements of the identifiers. * */ template void applyAliases(const SearchItem::PtrList& identifiers, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace) const; protected: ~TopDUContext() override; void clearFeaturesSatisfied(); void rebuildDynamicData(DUContext* parent, uint ownIndex) override; //Must be called after all imported top-contexts were loaded into the du-chain void rebuildDynamicImportStructure(); struct AliasChainElement; struct FindDeclarationsAcceptor; struct DeclarationChecker; struct ApplyAliasesBuddyInfo; template bool applyAliases(const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth) const; //Same as imports, without the slow access-check, for internal usage bool importsPrivate(const DUContext* origin, const CursorInRevision& position) const; DUCHAIN_DECLARE_DATA(TopDUContext) ///Called by DUChain::removeDocumentChain to destroy this top-context. void deleteSelf(); //Most of these classes need access to m_dynamicData friend class DUChain; friend class DUChainPrivate; friend class TopDUContextData; friend class TopDUContextLocalPrivate; friend class TopDUContextDynamicData; friend class Declaration; friend class DUContext; friend class Problem; friend class IndexedDeclaration; friend class IndexedDUContext; friend class LocalIndexedDeclaration; friend class LocalIndexedDUContext; friend class LocalIndexedProblem; friend class DeclarationId; friend class ParsingEnvironmentFile; TopDUContextLocalPrivate* m_local; class TopDUContextDynamicData* m_dynamicData; }; /** * Returns all uses of the given declaration within this top-context and all sub-contexts * */ KDEVPLATFORMLANGUAGE_EXPORT QVector allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges = false); inline uint qHash(const ReferencedTopDUContext& ctx) { return ctx.hash(); } } Q_DECLARE_TYPEINFO(KDevelop::ReferencedTopDUContext, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::ReferencedTopDUContext) #endif // KDEVPLATFORM_TOPDUCONTEXT_H diff --git a/kdevplatform/language/duchain/types/abstracttype.h b/kdevplatform/language/duchain/types/abstracttype.h index b75ffea8b7..f9c7bf9746 100644 --- a/kdevplatform/language/duchain/types/abstracttype.h +++ b/kdevplatform/language/duchain/types/abstracttype.h @@ -1,313 +1,313 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_ABSTRACTTYPE_H #define KDEVPLATFORM_ABSTRACTTYPE_H #include "typepointer.h" #include class QString; namespace KDevelop { class AbstractTypeData; class IndexedType; class TypeVisitor; class TypeExchanger; /// This macro is used to declare type-specific data-access functions within subclasses of AbstractType #define TYPE_DECLARE_DATA(Class) \ inline Class ## Data * d_func_dynamic() { makeDynamic(); return reinterpret_cast(d_ptr); } \ inline const Class ## Data* d_func() const { return reinterpret_cast(d_ptr); } /// This function creates a local variable named 'd' pointing to the data type (as shortcut) #define TYPE_D(Class) const Class ## Data * const d = d_func() #define TYPE_D_DYNAMIC(Class) Class ## Data * const d = d_func_dynamic() /** * \brief Base class for all types. * * The AbstractType class is a base class from which all types derive. It features: * - mechanisms for visiting types * - toString() feature * - equivalence feature * - cloning of types, and * - hashing and indexing * - efficient, persistent, and reference-counted storage of types using IndexedType * * Type classes are created in a way that allows storing them in memory or on disk * efficiently. They are classes which can store arbitrary lists immediately after their * private data structures in memory (thus enabling them to be mmapped or memcopied), * or being "dynamic" where you use exactly the same class and same access functions, * but the list data is stored in a temporary KDevVarLengthArray from a central repository, * until we save it back to the static memory-region again. * * When creating an own (sub-) type, you must: * - Override equals(..), hash(). * - The functions should _fully_ distinguish all types, * in regard to all stored information, and regarding their identity. * - This can be skipped if you're overriding a base-type which already incorporates * all of your own types status within its equals/hash functions (eg. you don't add own data). * - Implement a copy-constructor in which you copy the data from the source using copyData() * - Override the clone() function in which you use the copy-constructor to clone the type * - Add an enumerator "Identity" that contains an arbitrary unique identity value of the type * - Register your type in a source-file using REGISTER_TYPE(..), @see typeregister.h * - Add a typedef "Data", that contains the actual data of the type using the mechanisms described in appendedlist.h. * That data type must follow the same inheritance chain as the type itself, so it must be based on the Data object * of the base class. See AbstractTypeData. * - Use createData() to create the data-object in a constructor (which you then reach to the parent constructor) * - Use TYPE_DECLARE_DATA(YourType) to declare the data access functions d_func and d_func_dynamic, * and then use d_func()->.. and d_func_dynamic()->.. to access your type data * - Create a constructor that only takes a reference to the type data, and passes it to the parent type * * Every type can have only one other type as base-type, * but it can have additional base-classes that are not a direct part of the type-system(@see IdentifiedType). * * \sa appendedlist.h */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractType : public QSharedData { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /** * An enumeration of common modifiers for data types. * If you have any language-specific modifiers that don't belong here, * you can add them at/after LanguageSpecificModifier * @warning Think twice what information you store into the type-system. * The type-system should store information that is shared among many declarations, * and attributes of specific Declarations like public/private should be stored in * the Declarations themselves, not in the type-system. */ enum CommonModifiers : quint32 { NoModifiers = 0, ConstModifier = 1 << 0, VolatileModifier = 1 << 1, TransientModifier = 1 << 2, NewModifier = 1 << 3, SealedModifier = 1 << 4, UnsafeModifier = 1 << 5, FixedModifier = 1 << 6, ShortModifier = 1 << 7, LongModifier = 1 << 8, LongLongModifier = 1 << 9, SignedModifier = 1 << 10, UnsignedModifier = 1 << 11, LanguageSpecificModifier = 1 << 12 }; /// Constructor. AbstractType(); /// Constructor from data. explicit AbstractType(AbstractTypeData& dd); /// Destructor. virtual ~AbstractType (); /** * Access the type modifiers * * \returns the type's modifiers. */ quint32 modifiers() const; /** * Set the type's modifiers. * * \param modifiers modifiers of this type. */ void setModifiers(quint32 modifiers); /** * Visitor method. Called by TypeVisitor to visit the type heirachy. * Do not reimplement this, reimplement accept0 instead. * * \param v visitor which is calling this function. */ void accept(TypeVisitor* v) const; /** * Convenience visitor method which can be called with a null type. * * \param type type to visit, may be null. * \param v visitor which is visiting the given \a type */ static void acceptType(AbstractType::Ptr type, TypeVisitor* v); /** * Returns this type as a string, preferably the same as it is expressed in the code. * * \return this type as a string */ virtual QString toString() const; ///Must always be called before anything in the data pointer is changed! ///If it's not called beforehand, the type-repository gets corrupted void makeDynamic(); ///Should return whether this type's content equals the given one ///Since this is used by the type-repository, it must compare ALL members of the data type. virtual bool equals(const AbstractType* rhs) const; /** * Should create a clone of the source-type, with as much data copied as possible without breaking the du-chain. * */ virtual AbstractType* clone() const = 0; /** * A hash-value that should have the following properties: * - When two types match on equals(), it should be same. * - When two types don't match on equals(), it should be different with a high probability. * */ virtual uint hash() const; ///This can also be called on zero types, those can then be reconstructed from the zero index IndexedType indexed() const; /// Enumeration of major data types. enum WhichType : quint8 { TypeAbstract /**< an abstract type */, TypeIntegral /**< an integral */, TypePointer /**< a pointer*/, TypeReference /**< a reference */, TypeFunction /**< a function */, TypeStructure /**< a structure */, TypeArray /**< an array */, TypeDelayed /**< a delayed type */, TypeEnumeration /**< an enumeration type */, TypeEnumerator /**< an enumerator type */, TypeAlias /**< a type-alias type */, TypeUnsure /**< may represent multiple different types */ }; /** * Determine which data type this abstract type represents. * * \returns the data type represented by this type. */ virtual WhichType whichType() const; enum { Identity = 1 }; /** * Should, like accept0, be implemented by all types that hold references to other types. * * If this is called on one type, that type should call exchangeTypes(..) with all its referenced sub-types. * The type itself does not recurse into the sub-types, that can be done by the exchanger itself if desired. * */ virtual void exchangeTypes(TypeExchanger* exchanger); /** * Method to create copies of internal type data. You must use this to create the internal * data instances in copy constructors. It is needed, because it may need to allocate more memory * for appended lists. * * \param rhs data to copy * \returns copy of the data */ template static typename Type::Data& copyData(const typename Type::Data& rhs) { uint size; if (!rhs.m_dynamic) size = sizeof(typename Type::Data); //Create a dynamic data instance else size = rhs.dynamicSize(); //Create a constant data instance, that holds all the data embedded. typename Type::Data& ret(*new (new char[size]) typename Type::Data(rhs)); ret.template setTypeClassId(); return ret; } /** * As above, but does not support copying data into a lower class(Should not be used while cloning) */ template static DataType& copyDataDirectly(const DataType& rhs) { uint size; if (!rhs.m_dynamic) size = sizeof(DataType); //Create a dynamic data instance else size = rhs.dynamicSize(); //Create a constant data instance, that holds all the data embedded. return *new (new char[size]) DataType(rhs); } /** * Method to create internal data structures. Use this in normal constructors. * * \returns the internal data structure */ template static typename Type::Data& createData() { typename Type::Data& ret(*new (new char[sizeof(typename Type::Data)]) typename Type::Data()); ret.template setTypeClassId(); return ret; } - typedef AbstractTypeData Data; + using Data = AbstractTypeData; protected: /** * Visitor method, reimplement to allow visiting of types. * * \param v visitor which is visiting. */ virtual void accept0 (TypeVisitor* v) const = 0; /// toString() function which can provide \a spaceOnLeft rather than on right if desired. QString toString(bool spaceOnLeft) const; AbstractTypeData* d_ptr; TYPE_DECLARE_DATA(AbstractType) friend class AbstractTypeDataRequest; private: AbstractType(const AbstractType& rhs); }; /** * You can use these instead of dynamic_cast, for basic types it has better performance because it checks the whichType() member */ template inline To fastCast(AbstractType* from) { return dynamic_cast(from); } template inline const To fastCast(const AbstractType* from) { return const_cast(fastCast(const_cast(from))); //Hack so we don't need to define the functions twice, once for const, and once for not const } } #endif diff --git a/kdevplatform/language/duchain/types/arraytype.h b/kdevplatform/language/duchain/types/arraytype.h index b0ffd27666..a68fa85606 100644 --- a/kdevplatform/language/duchain/types/arraytype.h +++ b/kdevplatform/language/duchain/types/arraytype.h @@ -1,106 +1,106 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_ARRAYTYPE_H #define KDEVPLATFORM_ARRAYTYPE_H #include "abstracttype.h" namespace KDevelop { class ArrayTypeData; class KDEVPLATFORMLANGUAGE_EXPORT ArrayType : public AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /// Default constructor ArrayType(); /// Copy constructor. \param rhs type to copy ArrayType(const ArrayType& rhs); /// Constructor using raw data. \param data internal data. explicit ArrayType(ArrayTypeData& data); /// Destructor ~ArrayType() override; AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; /** * Retrieve the dimension of this array type. Multiple-dimensioned * arrays will have another array type as their elementType(). * * \returns the dimension of the array, or zero if the array is dimensionless (eg. int[]) */ int dimension () const; /** * Set this array type's dimension. * If @p dimension is zero, the array is considered dimensionless (eg. int[]). * * \param dimension new dimension, set to zero for a dimensionless type (eg. int[]) */ void setDimension(int dimension); /** * Retrieve the element type of the array, e.g. "int" for int[3]. * * \returns the element type. */ AbstractType::Ptr elementType () const; /** * Set the element type of the array, e.g. "int" for int[3]. */ void setElementType(const AbstractType::Ptr& type); QString toString() const override; uint hash() const override; WhichType whichType() const override; void exchangeTypes(TypeExchanger* exchanger) override; enum { Identity = 7 }; - typedef ArrayTypeData Data; + using Data = ArrayTypeData; protected: void accept0 (TypeVisitor* v) const override; TYPE_DECLARE_DATA(ArrayType) }; template <> inline ArrayType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeArray) return nullptr; else return static_cast(from); } } #endif // KDEVPLATFORM_TYPESYSTEM_H diff --git a/kdevplatform/language/duchain/types/constantintegraltype.h b/kdevplatform/language/duchain/types/constantintegraltype.h index 0fc7a464b1..4d0b709707 100644 --- a/kdevplatform/language/duchain/types/constantintegraltype.h +++ b/kdevplatform/language/duchain/types/constantintegraltype.h @@ -1,121 +1,121 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat 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. */ #ifndef KDEVPLATFORM_CONSTANTINTEGRALTYPE_H #define KDEVPLATFORM_CONSTANTINTEGRALTYPE_H #include "integraltype.h" #include "typesystemdata.h" namespace KDevelop { template T constant_value(const qint64* realval) { T value; memcpy(&value, realval, sizeof(T)); return value; } class KDEVPLATFORMLANGUAGE_EXPORT ConstantIntegralType : public IntegralType { public: ConstantIntegralType(const ConstantIntegralType& rhs); explicit ConstantIntegralType(ConstantIntegralTypeData& data); explicit ConstantIntegralType(uint type = TypeNone); - typedef TypePtr Ptr; + using Ptr = TypePtr; /**The types and modifiers are not changed! * The values are casted internally to the local representation, so you can lose precision. * */ template void setValue(ValueType value) { if (AbstractType::modifiers() & UnsignedModifier) setValueInternal(value); else if (IntegralType::dataType() == TypeFloat) setValueInternal(value); else if (IntegralType::dataType() == TypeDouble) setValueInternal(value); else setValueInternal(value); } /** * For booleans, the value is 1 for true, and 0 for false. * All signed values should be retrieved and set through value(), * * */ template ValueType value() const { if (modifiers() & UnsignedModifier) { return constant_value(&d_func()->m_value); } else if (dataType() == TypeFloat) { return constant_value(&d_func()->m_value); } else if (dataType() == TypeDouble) { return constant_value(&d_func()->m_value); } else { return constant_value(&d_func()->m_value); } } qint64 plainValue() const; QString toString() const override; QString valueAsString() const; bool equals(const KDevelop::AbstractType* rhs) const override; KDevelop::AbstractType* clone() const override; uint hash() const override; enum { Identity = 14 }; - typedef ConstantIntegralTypeData Data; + using Data = ConstantIntegralTypeData; protected: TYPE_DECLARE_DATA(ConstantIntegralType); private: //Sets the value without casting template void setValueInternal(ValueType value); }; template <> inline ConstantIntegralType* fastCast(AbstractType* from) { if (!from || from->whichType() != KDevelop::AbstractType::TypeIntegral) return nullptr; else return dynamic_cast(from); } } #endif // KDEVPLATFORM_CONSTANTINTEGRALTYPE_H diff --git a/kdevplatform/language/duchain/types/containertypes.h b/kdevplatform/language/duchain/types/containertypes.h index 2e41af936a..1945fb3302 100644 --- a/kdevplatform/language/duchain/types/containertypes.h +++ b/kdevplatform/language/duchain/types/containertypes.h @@ -1,220 +1,220 @@ /* * This file is part of KDevelop * Copyright (C) 2011-2014 Sven Brauch * * 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, see . */ #ifndef KDEVPLATFORM_CONTAINER_TYPES_H #define KDEVPLATFORM_CONTAINER_TYPES_H #include "structuretype.h" #include "typesystemdata.h" #include "typeutils.h" #include "../duchainlock.h" #include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ListTypeData : public KDevelop::StructureTypeData { public: ListTypeData() : KDevelop::StructureTypeData() , m_contentType() { } ListTypeData(const ListTypeData& rhs) : KDevelop::StructureTypeData(rhs) , m_contentType(rhs.m_contentType) { } explicit ListTypeData(const KDevelop::StructureTypeData& rhs) : KDevelop::StructureTypeData(rhs) , m_contentType() { } IndexedType m_contentType; }; /** * @brief Represents a list-like object which can have a content type. * * Example for Python: * @code * # in the file describing the built-in list type * class List: # (1) * pass * * # in the user's code: * a = [] # (2) * a.append(3) # (3) * @endcode * * This type class can be used to determine the type of a as "list of int" as follows: * (1) When creating the type for the List class, * create a ListType instead of a structure type. * (Your language plugin somehow needs to know * which classes are list-like; Python does this * through special comments for the class) * (2) Set the type of a to the type declared by the * List class, as usual. * (3) Call * \code * static_cast(a->abstractType())->addContentType(int_type) * \endcode * (Your language plugin needs to know which methods * add their argument's content to the type's content; * Python does this through special comments for the method) */ class KDEVPLATFORMLANGUAGE_EXPORT ListType : public KDevelop::StructureType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; ListType(); ListType(const ListType& rhs); explicit ListType(StructureTypeData& data); /** * @brief Adds @p typeToAdd to the content type of this list. * * If the list currently has no type, it is set to this type. * If @p typeToAdd equals this list's content type, or is a useless type * nothing happens. * Otherwise, the type of the list becomes an unsure type of the previous * and @p typeToAdd. * * Pass your language's UnsureType as a template parameter, as it will eventually * need to be instantiated. * * @note If the type is already assigned to a declaration, the duchain * must be write-locked when this is called. * * @param typeToAdd The new type the list's contents can possibly be of. */ template void addContentType(const AbstractType::Ptr& typeToAdd) { auto newContentType = TypeUtils::mergeTypes(contentType().abstractType(), typeToAdd); d_func_dynamic()->m_contentType = IndexedType(newContentType); } /** * @brief Replaces this list's content type by @p newType. */ void replaceContentType(const AbstractType::Ptr& newType); /** * @brief Get this lists's content type. */ IndexedType contentType() const; /** * @brief Return only the container type, not the content type in a string. */ QString containerToString() const; /** * @brief Formats this type (base type and content type) in a string. */ QString toString() const override; AbstractType* clone() const override; uint hash() const override; bool equals(const AbstractType* rhs) const override; enum { Identity = 58 }; - typedef ListTypeData Data; + using Data = ListTypeData; protected: TYPE_DECLARE_DATA(ListType); }; class KDEVPLATFORMLANGUAGE_EXPORT MapTypeData : public ListTypeData { public: MapTypeData() : ListTypeData() , m_keyType() { } MapTypeData(const MapTypeData& rhs) : ListTypeData(rhs) , m_keyType(rhs.m_keyType) { } explicit MapTypeData(const ListTypeData& rhs) : ListTypeData(rhs) , m_keyType() { } IndexedType m_keyType; }; /** * @brief Represents a hashmap-like object which can have a key and a content type. * * @see ListType * This works the same as ListType, except that you can also track the object's key type. */ class KDEVPLATFORMLANGUAGE_EXPORT MapType : public ListType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; MapType(); MapType(const MapType& rhs); explicit MapType(ListTypeData& data); /** * @brief Add @p typeToAdd to this map's key type. * Behaves like addContentType, except that it modifies the key type instead. */ template void addKeyType(const AbstractType::Ptr& typeToAdd) { auto newKeyType = TypeUtils::mergeTypes(keyType().abstractType(), typeToAdd); DUChainWriteLocker lock; d_func_dynamic()->m_keyType = IndexedType(newKeyType); } /** * @brief Set this map's key type to @p newType. */ void replaceKeyType(const AbstractType::Ptr& newType); /** * @brief Get this map's key type. */ IndexedType keyType() const; /** * @brief Formats this type (base type and key+content type) in a string. */ QString toString() const override; AbstractType* clone() const override; uint hash() const override; bool equals(const AbstractType* rhs) const override; enum { Identity = 57 }; - typedef MapTypeData Data; + using Data = MapTypeData; protected: TYPE_DECLARE_DATA(MapType); }; } // namespace KDevelop #endif // KDEVPLATFORM_CONTAINER_TYPES_H diff --git a/kdevplatform/language/duchain/types/delayedtype.h b/kdevplatform/language/duchain/types/delayedtype.h index 997c2dae01..abd695685b 100644 --- a/kdevplatform/language/duchain/types/delayedtype.h +++ b/kdevplatform/language/duchain/types/delayedtype.h @@ -1,107 +1,107 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_DELAYEDTYPE_H #define KDEVPLATFORM_DELAYEDTYPE_H #include "abstracttype.h" #include "../identifier.h" namespace KDevelop { class DelayedTypeData; /** * \short A type which has not yet been resolved. * * Delayed types can be used for any types that cannot be resolved in the moment they are encountered. * They can be used for example in template-classes, or to store the names of unresolved types. * In a template-class, many types can not be evaluated at the time they are used, because they depend on unknown template-parameters. * Delayed types store the way the type would be searched, and can be used to find the type once the template-paremeters have values. * */ class KDEVPLATFORMLANGUAGE_EXPORT DelayedType : public KDevelop::AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /// An enumeration of enum Kind : quint8 { Delayed /**< The type should be resolved later. This is the default. */, Unresolved /**< The type could not be resolved */ }; /// Default constructor DelayedType(); /// Copy constructor. \param rhs type to copy DelayedType(const DelayedType& rhs); /// Constructor using raw data. \param data internal data. explicit DelayedType(DelayedTypeData& data); /// Destructor ~DelayedType() override; /** * Access the type identifier which this type represents. * * \returns the type identifier. */ KDevelop::IndexedTypeIdentifier identifier() const; /** * Set the type identifier which this type represents. * * \param identifier the type identifier. */ void setIdentifier(const KDevelop::IndexedTypeIdentifier& identifier); QString toString() const override; AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; Kind kind() const; void setKind(Kind kind); uint hash() const override; WhichType whichType() const override; enum { Identity = 8 }; - typedef DelayedTypeData Data; + using Data = DelayedTypeData; protected: void accept0 (KDevelop::TypeVisitor* v) const override; TYPE_DECLARE_DATA(DelayedType) }; template <> inline DelayedType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeDelayed) return nullptr; else return static_cast(from); } } #endif diff --git a/kdevplatform/language/duchain/types/enumerationtype.h b/kdevplatform/language/duchain/types/enumerationtype.h index 92dc7b28f5..05cb960d64 100644 --- a/kdevplatform/language/duchain/types/enumerationtype.h +++ b/kdevplatform/language/duchain/types/enumerationtype.h @@ -1,75 +1,75 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat 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. */ #ifndef KDEVPLATFORM_ENUMERATIONTYPE_H #define KDEVPLATFORM_ENUMERATIONTYPE_H #include "integraltype.h" #include "identifiedtype.h" #include "typesystemdata.h" // for IntegralTypeData (used in EnumerationTypeBase) namespace KDevelop { -typedef KDevelop::MergeIdentifiedType EnumerationTypeBase; +using EnumerationTypeBase = KDevelop::MergeIdentifiedType; -typedef EnumerationTypeBase::Data EnumerationTypeData; +using EnumerationTypeData = EnumerationTypeBase::Data; class KDEVPLATFORMLANGUAGE_EXPORT EnumerationType : public EnumerationTypeBase { public: EnumerationType(); EnumerationType(const EnumerationType& rhs); explicit EnumerationType(EnumerationTypeData& data); - typedef TypePtr Ptr; + using Ptr = TypePtr; uint hash() const override; KDevelop::AbstractType* clone() const override; bool equals(const KDevelop::AbstractType* rhs) const override; QString toString() const override; WhichType whichType() const override; enum { Identity = 21 }; - typedef EnumerationTypeData Data; + using Data = EnumerationTypeData; protected: TYPE_DECLARE_DATA(EnumerationType); }; template <> inline EnumerationType* fastCast(AbstractType* from) { if (!from || from->whichType() != KDevelop::AbstractType::TypeEnumeration) return nullptr; else return static_cast(from); } } #endif // KDEVPLATFORM_ENUMERATIONTYPE_H diff --git a/kdevplatform/language/duchain/types/enumeratortype.h b/kdevplatform/language/duchain/types/enumeratortype.h index 86e8fd21e8..40e061a6fb 100644 --- a/kdevplatform/language/duchain/types/enumeratortype.h +++ b/kdevplatform/language/duchain/types/enumeratortype.h @@ -1,76 +1,76 @@ /* This file is part of KDevelop Copyright 2002-2005 Roberto Raggi Copyright 2006 Adam Treat 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. */ #ifndef KDEVPLATFORM_ENUMERATORTYPE_H #define KDEVPLATFORM_ENUMERATORTYPE_H #include "identifiedtype.h" #include "constantintegraltype.h" namespace KDevelop { -typedef KDevelop::MergeIdentifiedType EnumeratorTypeBase; +using EnumeratorTypeBase = KDevelop::MergeIdentifiedType; -typedef EnumeratorTypeBase::Data EnumeratorTypeData; +using EnumeratorTypeData = EnumeratorTypeBase::Data; //The same as EnumerationType, with the difference that here the value is also known class KDEVPLATFORMLANGUAGE_EXPORT EnumeratorType : public EnumeratorTypeBase { public: EnumeratorType(const EnumeratorType& rhs); explicit EnumeratorType(EnumeratorTypeData& data); EnumeratorType(); - typedef TypePtr Ptr; + using Ptr = TypePtr; bool equals(const KDevelop::AbstractType* rhs) const override; KDevelop::AbstractType* clone() const override; uint hash() const override; WhichType whichType() const override; QString toString() const override; enum { Identity = 20 }; - typedef EnumeratorTypeData Data; + using Data = EnumeratorTypeData; protected: TYPE_DECLARE_DATA(EnumeratorType); }; template <> inline EnumeratorType* fastCast(AbstractType* from) { if (!from || from->whichType() != KDevelop::AbstractType::TypeEnumerator) return nullptr; else return static_cast(from); } } #endif // KDEVPLATFORM_ENUMERATORTYPE_H diff --git a/kdevplatform/language/duchain/types/functiontype.h b/kdevplatform/language/duchain/types/functiontype.h index f979a1ab2d..a786eee7a0 100644 --- a/kdevplatform/language/duchain/types/functiontype.h +++ b/kdevplatform/language/duchain/types/functiontype.h @@ -1,149 +1,149 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_FUNCTIONTYPE_H #define KDEVPLATFORM_FUNCTIONTYPE_H #include "abstracttype.h" namespace KDevelop { class FunctionTypeData; /** * \short A type representing function types. * * A FunctionType is represents the type of a function. It provides access * to the return type, and number and types of the arguments. */ class KDEVPLATFORMLANGUAGE_EXPORT FunctionType : public AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /// An enumeration of sections of the function signature that can be returned. enum SignaturePart { SignatureWhole /**< When this is given to toString(..), a string link "RETURNTYPE (ARGTYPE1, ARGTYPE1, ..)" is returned */, SignatureReturn /**< When this is given, only a string that represents the return-type is returned */, SignatureArguments /**< When this is given, a string that represents the arguments like "(ARGTYPE1, ARGTYPE1, ..)" * is returend. This does _not_ include a trailing "const" if the function is constant */ }; /// Default constructor FunctionType(); /// Copy constructor. \param rhs type to copy FunctionType(const FunctionType& rhs); /// Constructor using raw data. \param data internal data. explicit FunctionType(FunctionTypeData& data); /// Destructor ~FunctionType() override; /** * Retrieve the return type of the function. * * \returns the return type. */ AbstractType::Ptr returnType () const; /** * Sets the return type of the function. * * \param returnType the return type. */ void setReturnType(const AbstractType::Ptr& returnType); /** * Retrieve the list of types of the function's arguments. * * \returns the argument types. */ QList arguments () const; /** * Returns the same arguemtns as arguments(), but without converting them to a QList. * This is much faster, and should be preferred for very tight loops when the performance counts. * \return an array that contains the arguments. For the count of arguments, call indexedArgumentsSize */ const IndexedType* indexedArguments() const; /** * Returns the size of the array returned by indexedArguments(). This is much faster than working with arguments(). */ uint indexedArgumentsSize() const; /** * Add an argument to the function, specifying what type it takes. * * \param argument the argument's type * \param index where to insert the argument; the default "-1" will insert it at the end of the list */ void addArgument(const AbstractType::Ptr& argument, int index = -1); /** * Remove the argument with number i from the function. * * \param i index (starting from 0 with the first argument) to remove */ void removeArgument(int i); AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; QString toString() const override; /** * This function creates a string that represents the requested part of * this function's signature. * * \param sigPart part of the signature requested. * \returns the signature as text. */ virtual QString partToString(SignaturePart sigPart) const; uint hash() const override; WhichType whichType() const override; void exchangeTypes(TypeExchanger* exchanger) override; enum { Identity = 5 }; - typedef FunctionTypeData Data; + using Data = FunctionTypeData; protected: void accept0 (TypeVisitor* v) const override; TYPE_DECLARE_DATA(FunctionType) }; template <> inline FunctionType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeFunction) return nullptr; else return static_cast(from); } } #endif diff --git a/kdevplatform/language/duchain/types/integraltype.h b/kdevplatform/language/duchain/types/integraltype.h index c2f093dd36..c42740fda6 100644 --- a/kdevplatform/language/duchain/types/integraltype.h +++ b/kdevplatform/language/duchain/types/integraltype.h @@ -1,126 +1,126 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_INTEGRALTYPE_H #define KDEVPLATFORM_INTEGRALTYPE_H #include "abstracttype.h" namespace KDevelop { class IntegralTypeData; /** * \short A type representing inbuilt data types. * * IntegralType is used to represent types which are native to a programming languge, * such as (e.g.) int, float, double, char, bool etc. */ class KDEVPLATFORMLANGUAGE_EXPORT IntegralType : public AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /** * Enumeration of frequently used integral types. * If your language has another integral type not listed here, * you can create custom types staring from TypeLanguageSpecific. */ enum CommonIntegralTypes { TypeVoid, TypeNone, TypeNull, TypeChar, TypeBoolean, TypeByte, TypeSbyte, TypeShort, TypeInt, TypeLong, TypeFloat, TypeDouble, TypeWchar_t, TypeString, TypeMixed, TypeChar16_t, TypeChar32_t, TypeLanguageSpecific = 200 }; /// Default constructor explicit IntegralType(uint type = TypeNone); /// Copy constructor. \param rhs type to copy IntegralType(const IntegralType& rhs); /// Constructor using raw data. \param data internal data. explicit IntegralType(IntegralTypeData& data); /// Destructor ~IntegralType() override; /** * Access the integral type * * \returns the type's data type. */ uint dataType() const; /** * Set the type's data type. * * \param dataType data type of this type. */ void setDataType(uint dataType); /** * TODO: think of a way to make @c toString work properly for custom data types * right now you need to create a custom type and overload it... */ QString toString() const override; uint hash() const override; WhichType whichType() const override; AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; enum { Identity = 2 }; - typedef IntegralTypeData Data; + using Data = IntegralTypeData; protected: void accept0 (TypeVisitor* v) const override; TYPE_DECLARE_DATA(IntegralType) }; template <> inline IntegralType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeIntegral) return nullptr; else return static_cast(from); } } #endif diff --git a/kdevplatform/language/duchain/types/pointertype.h b/kdevplatform/language/duchain/types/pointertype.h index 84024b8c2c..2b2cf1732a 100644 --- a/kdevplatform/language/duchain/types/pointertype.h +++ b/kdevplatform/language/duchain/types/pointertype.h @@ -1,98 +1,98 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_POINTERTYPE_H #define KDEVPLATFORM_POINTERTYPE_H #include "abstracttype.h" namespace KDevelop { class PointerTypeData; /** * \short A type representing pointer types. * * PointerType is used to represent types which hold a pointer to a location * in memory. */ class KDEVPLATFORMLANGUAGE_EXPORT PointerType : public AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /// Default constructor PointerType (); /// Copy constructor. \param rhs type to copy PointerType(const PointerType& rhs); /// Constructor using raw data. \param data internal data. explicit PointerType(PointerTypeData& data); /// Destructor ~PointerType() override; /** * Sets the base type of the pointer, ie. what type of data the pointer points to. * * \param type the base type. */ void setBaseType(const AbstractType::Ptr& type); /** * Retrieve the base type of the pointer, ie. what type of data the pointer points to. * * \returns the base type. */ AbstractType::Ptr baseType () const; QString toString() const override; uint hash() const override; WhichType whichType() const override; AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; void exchangeTypes(TypeExchanger* exchanger) override; enum { Identity = 3 }; - typedef PointerTypeData Data; + using Data = PointerTypeData; protected: void accept0 (TypeVisitor* v) const override; TYPE_DECLARE_DATA(PointerType) }; template <> inline PointerType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypePointer) return nullptr; else return static_cast(from); } } #endif diff --git a/kdevplatform/language/duchain/types/referencetype.h b/kdevplatform/language/duchain/types/referencetype.h index cf89b67b28..df55ada2a8 100644 --- a/kdevplatform/language/duchain/types/referencetype.h +++ b/kdevplatform/language/duchain/types/referencetype.h @@ -1,112 +1,112 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi 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. */ #ifndef KDEVPLATFORM_REFERENCETYPE_H #define KDEVPLATFORM_REFERENCETYPE_H #include "abstracttype.h" namespace KDevelop { class ReferenceTypeData; /** * \short A type representing reference types. * * ReferenceType is used to represent types which hold a reference to a * variable. */ class KDEVPLATFORMLANGUAGE_EXPORT ReferenceType : public AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /// Default constructor ReferenceType (); /// Copy constructor. \param rhs type to copy ReferenceType (const ReferenceType& rhs); /// Constructor using raw data. \param data internal data. explicit ReferenceType(ReferenceTypeData& data); /// Destructor ~ReferenceType() override; /** * Retrieve the referenced type, ie. what type of data this type references. * * \returns the base type. */ AbstractType::Ptr baseType () const; /** * Sets the referenced type, ie. what type of data this type references. * * \param baseType the base type. */ void setBaseType(const AbstractType::Ptr& baseType); /** * Checks whether this type is an rvalue- or lvalue-reference type. * * \returns true for rvalue-references, false for lvalue-references */ bool isRValue() const; /** * Sets whether this type is an rvalue- or lvalue-reference type. * * \param isRValue true for rvalue-references, false for lvalue-references */ void setIsRValue(bool isRValue); QString toString() const override; uint hash() const override; WhichType whichType() const override; AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; void exchangeTypes(TypeExchanger* exchanger) override; enum { Identity = 4 }; - typedef ReferenceTypeData Data; + using Data = ReferenceTypeData; protected: void accept0 (TypeVisitor* v) const override; TYPE_DECLARE_DATA(ReferenceType) }; template <> inline ReferenceType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeReference) return nullptr; else return static_cast(from); } } #endif // KDEVPLATFORM_TYPESYSTEM_H diff --git a/kdevplatform/language/duchain/types/structuretype.h b/kdevplatform/language/duchain/types/structuretype.h index c5fe7dd5ec..b67056da76 100644 --- a/kdevplatform/language/duchain/types/structuretype.h +++ b/kdevplatform/language/duchain/types/structuretype.h @@ -1,87 +1,87 @@ /* This file is part of KDevelop Copyright 2006 Roberto Raggi Copyright 2006-2008 Hamish Rodda 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 KDEVPLATFORM_STRUCTURETYPE_H #define KDEVPLATFORM_STRUCTURETYPE_H #include "abstracttype.h" #include "identifiedtype.h" #include "typesystemdata.h" namespace KDevelop { class StructureTypeData; -typedef MergeIdentifiedType StructureTypeBase; +using StructureTypeBase = MergeIdentifiedType; /** * \short A type representing structure types. * * StructureType represents all structures, including classes, * interfaces, etc. */ class KDEVPLATFORMLANGUAGE_EXPORT StructureType : public StructureTypeBase { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; /// Default constructor StructureType(); /// Copy constructor. \param rhs type to copy StructureType(const StructureType& rhs); /// Constructor using raw data. \param data internal data. explicit StructureType(StructureTypeData& data); /// Destructor ~StructureType() override; AbstractType* clone() const override; bool equals(const AbstractType* rhs) const override; QString toString() const override; uint hash() const override; WhichType whichType() const override; //virtual void exchangeTypes(KDevelop::TypeExchanger*); enum { Identity = 6 }; - typedef StructureTypeData Data; + using Data = StructureTypeData; protected: void accept0 (TypeVisitor* v) const override; TYPE_DECLARE_DATA(StructureType) }; template <> inline StructureType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeStructure) return nullptr; else return static_cast(from); } } #endif diff --git a/kdevplatform/language/duchain/types/typealiastype.h b/kdevplatform/language/duchain/types/typealiastype.h index c481e8d98c..d5ead9a958 100644 --- a/kdevplatform/language/duchain/types/typealiastype.h +++ b/kdevplatform/language/duchain/types/typealiastype.h @@ -1,89 +1,89 @@ /* 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 KDEVPLATFORM_TYPEALIASTYPE_H #define KDEVPLATFORM_TYPEALIASTYPE_H #include "abstracttype.h" #include "identifiedtype.h" #include "typeregister.h" #include namespace KDevelop { class TypeAliasTypeData; -typedef MergeIdentifiedType TypeAliasTypeBase; +using TypeAliasTypeBase = MergeIdentifiedType; class KDEVPLATFORMLANGUAGE_EXPORT TypeAliasType : public TypeAliasTypeBase { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; TypeAliasType(const TypeAliasType& rhs) : TypeAliasTypeBase(copyData(*rhs.d_func())) { } explicit TypeAliasType(TypeAliasTypeData& data) : TypeAliasTypeBase(data) { } TypeAliasType() : TypeAliasTypeBase(createData()) { } KDevelop::AbstractType::Ptr type() const; void setType(const KDevelop::AbstractType::Ptr& type); uint hash() const override; QString toString() const override; // virtual QString mangled() const; void exchangeTypes(KDevelop::TypeExchanger* exchanger) override; KDevelop::AbstractType* clone() const override; bool equals(const KDevelop::AbstractType* rhs) const override; KDevelop::AbstractType::WhichType whichType() const override; enum { Identity = 9 }; - typedef TypeAliasTypeData Data; + using Data = TypeAliasTypeData; protected: TYPE_DECLARE_DATA(TypeAliasType); void accept0 (KDevelop::TypeVisitor* v) const override; }; template <> inline TypeAliasType* fastCast(AbstractType* from) { if (!from || from->whichType() != AbstractType::TypeIntegral) return nullptr; else return static_cast(from); } } #endif // KDEVPLATFORM_TYPEALIASTYPE_H diff --git a/kdevplatform/language/duchain/types/unsuretype.h b/kdevplatform/language/duchain/types/unsuretype.h index da4ea3caaf..f151ada22e 100644 --- a/kdevplatform/language/duchain/types/unsuretype.h +++ b/kdevplatform/language/duchain/types/unsuretype.h @@ -1,92 +1,92 @@ /* 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 KDEVPLATFORM_UNSURETYPE_H #define KDEVPLATFORM_UNSURETYPE_H #include "abstracttype.h" #include "typesystemdata.h" #include "../appendedlist.h" #include namespace KDevelop { KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(UnsureTypeData, m_types, IndexedType) struct KDEVPLATFORMLANGUAGE_EXPORT UnsureTypeData : public AbstractTypeData { UnsureTypeData() { initializeAppendedLists(m_dynamic); } ~UnsureTypeData() { freeAppendedLists(); } UnsureTypeData(const UnsureTypeData& rhs) : AbstractTypeData(rhs) { initializeAppendedLists(m_dynamic); copyListsFrom(rhs); } START_APPENDED_LISTS_BASE(UnsureTypeData, AbstractTypeData) APPENDED_LIST_FIRST(UnsureTypeData, IndexedType, m_types) END_APPENDED_LISTS(UnsureTypeData, m_types) }; class KDEVPLATFORMLANGUAGE_EXPORT UnsureType : public AbstractType { public: - typedef TypePtr Ptr; + using Ptr = TypePtr; UnsureType(const UnsureType& rhs); UnsureType(); explicit UnsureType(UnsureTypeData& data); KDevelop::AbstractType* clone() const override; QString toString() const override; bool equals(const KDevelop::AbstractType* rhs) const override; uint hash() const override; KDevelop::AbstractType::WhichType whichType() const override; void exchangeTypes(KDevelop::TypeExchanger* exchanger) override; virtual void addType(const IndexedType& type); virtual void removeType(const IndexedType& type); ///Array of represented types. You can conveniently iterate it using the FOREACH_FUNCTION macro, ///or just access them using indices const IndexedType* types() const; ///Count of types accessible through types() uint typesSize() const; enum { Identity = 39 }; - typedef UnsureTypeData Data; + using Data = UnsureTypeData; protected: TYPE_DECLARE_DATA(UnsureType) void accept0(KDevelop::TypeVisitor* v) const override; }; } #endif // KDEVPLATFORM_UNSURETYPE_H diff --git a/kdevplatform/language/editor/modificationrevision.cpp b/kdevplatform/language/editor/modificationrevision.cpp index ad704eb032..322cd18704 100644 --- a/kdevplatform/language/editor/modificationrevision.cpp +++ b/kdevplatform/language/editor/modificationrevision.cpp @@ -1,145 +1,145 @@ /* 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 "modificationrevision.h" #include #include #include #include "modificationrevisionset.h" #include /// @todo Listen to filesystem changes (together with the project manager) /// and call fileModificationCache().clear(...) when a file has changed using namespace KDevelop; const int KDevelop::cacheModificationTimesForSeconds = 30; QMutex fileModificationTimeCacheMutex(QMutex::Recursive); struct FileModificationCache { QDateTime m_readTime; QDateTime m_modificationTime; }; Q_DECLARE_TYPEINFO(FileModificationCache, Q_MOVABLE_TYPE); -typedef QHash FileModificationMap; +using FileModificationMap = QHash; FileModificationMap& fileModificationCache() { static FileModificationMap cache; return cache; } -typedef QHash OpenDocumentRevisionsMap; +using OpenDocumentRevisionsMap = QHash; OpenDocumentRevisionsMap& openDocumentsRevisionMap() { static OpenDocumentRevisionsMap map; return map; } QDateTime fileModificationTimeCached(const IndexedString& fileName) { const auto currentTime = QDateTime::currentDateTime(); auto it = fileModificationCache().constFind(fileName); if (it != fileModificationCache().constEnd()) { ///Use the cache for X seconds if (it.value().m_readTime.secsTo(currentTime) < cacheModificationTimesForSeconds) { return it.value().m_modificationTime; } } QFileInfo fileInfo(fileName.str()); FileModificationCache data = {currentTime, fileInfo.lastModified()}; fileModificationCache().insert(fileName, data); return data.m_modificationTime; } void ModificationRevision::clearModificationCache(const IndexedString& fileName) { ///@todo Make the cache management more clever (don't clear the whole) ModificationRevisionSet::clearCache(); QMutexLocker lock(&fileModificationTimeCacheMutex); fileModificationCache().remove(fileName); } ModificationRevision ModificationRevision::revisionForFile(const IndexedString& url) { QMutexLocker lock(&fileModificationTimeCacheMutex); ModificationRevision ret(fileModificationTimeCached(url)); OpenDocumentRevisionsMap::const_iterator it = openDocumentsRevisionMap().constFind(url); if (it != openDocumentsRevisionMap().constEnd()) { ret.revision = it.value(); } return ret; } void ModificationRevision::clearEditorRevisionForFile(const KDevelop::IndexedString& url) { ModificationRevisionSet::clearCache(); ///@todo Make the cache management more clever (don't clear the whole) QMutexLocker lock(&fileModificationTimeCacheMutex); openDocumentsRevisionMap().remove(url); } void ModificationRevision::setEditorRevisionForFile(const KDevelop::IndexedString& url, int revision) { ModificationRevisionSet::clearCache(); ///@todo Make the cache management more clever (don't clear the whole) QMutexLocker lock(&fileModificationTimeCacheMutex); openDocumentsRevisionMap().insert(url, revision); Q_ASSERT(revisionForFile(url).revision == revision); } ModificationRevision::ModificationRevision(const QDateTime& modTime, int revision_) : modificationTime(modTime.toTime_t()) , revision(revision_) { } bool ModificationRevision::operator <(const ModificationRevision& rhs) const { return modificationTime < rhs.modificationTime || (modificationTime == rhs.modificationTime && revision < rhs.revision); } bool ModificationRevision::operator ==(const ModificationRevision& rhs) const { return modificationTime == rhs.modificationTime && revision == rhs.revision; } bool ModificationRevision::operator !=(const ModificationRevision& rhs) const { return modificationTime != rhs.modificationTime || revision != rhs.revision; } QString ModificationRevision::toString() const { return QStringLiteral("%1 (rev %2)").arg(QDateTime::fromTime_t(modificationTime).time().toString()).arg(revision); } diff --git a/kdevplatform/language/editor/modificationrevisionset.cpp b/kdevplatform/language/editor/modificationrevisionset.cpp index d87727ddca..347f1eaf93 100644 --- a/kdevplatform/language/editor/modificationrevisionset.cpp +++ b/kdevplatform/language/editor/modificationrevisionset.cpp @@ -1,362 +1,361 @@ /* 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 "modificationrevisionset.h" #include #include #include #include //When uncommented, the reason for needed updates is printed // #define DEBUG_NEEDSUPDATE namespace KDevelop { QMutex modificationRevisionSetMutex(QMutex::Recursive); struct FileModificationPair { KDevelop::IndexedString file; KDevelop::ModificationRevision revision; FileModificationPair() { } FileModificationPair(const KDevelop::IndexedString& _file, KDevelop::ModificationRevision _revision) : file(_file) , revision(_revision) { } unsigned int hash() const { return ((file.hash() + revision.modificationTime) * 17 + revision.revision) * 73; } unsigned short int itemSize() const { return sizeof(FileModificationPair); } bool operator==(const FileModificationPair& rhs) const { return file == rhs.file && revision == rhs.revision; } }; struct FileModificationPairRequest { FileModificationPairRequest(const FileModificationPair& data) : m_data(data) { } const FileModificationPair& m_data; enum { AverageSize = sizeof(FileModificationPair) }; unsigned int hash() const { return m_data.hash(); } uint itemSize() const { return m_data.itemSize(); } void createItem(FileModificationPair* item) const { new (item) FileModificationPair(m_data); } bool equals(const FileModificationPair* item) const { return *item == m_data; } static void destroy(FileModificationPair* item, KDevelop::AbstractItemRepository&) { item->~FileModificationPair(); } static bool persistent(const FileModificationPair* /*item*/) { return true; //Reference-counting is done implicitly using the set-repository } }; -typedef KDevelop::ItemRepository FileModificationPairRepository; +using FileModificationPairRepository = KDevelop::ItemRepository; static FileModificationPairRepository& fileModificationPairRepository() { static FileModificationPairRepository rep(QStringLiteral("file modification repository")); rep.setMutex(&modificationRevisionSetMutex); return rep; } void initModificationRevisionSetRepository() { fileModificationPairRepository(); } QHash> needsUpdateCache; void ModificationRevisionSet::clearCache() { QMutexLocker lock(&modificationRevisionSetMutex); ///@todo More intelligent clearing. We actually need to watch the directory for changes, and if there are changes, clear the cache. needsUpdateCache.clear(); } struct FileModificationSetRepository : public Utils::BasicSetRepository { FileModificationSetRepository() : Utils::BasicSetRepository(QStringLiteral( "file modification sets"), &globalItemRepositoryRegistry(), true) { } void itemRemovedFromSets(uint index) override; }; //FileModificationSetRepository fileModificationSetRepository; struct FileModificationSetRepositoryRepresenter { static FileModificationSetRepository& repository() { static FileModificationSetRepository fileModificationSetRepository; return fileModificationSetRepository; } }; ModificationRevisionSet::ModificationRevisionSet(unsigned int index) : m_index(index) { } uint ModificationRevisionSet::size() const { Utils::Set set = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); return set.count(); } void ModificationRevisionSet::clear() { QMutexLocker lock(&modificationRevisionSetMutex); if (m_index) { Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); oldModificationTimes.staticUnref(); m_index = 0; } } void ModificationRevisionSet::addModificationRevision(const IndexedString& url, const KDevelop::ModificationRevision& revision) { QMutexLocker lock(&modificationRevisionSetMutex); if (m_index == 0) { Utils::Set set = FileModificationSetRepositoryRepresenter::repository().createSet( fileModificationPairRepository().index(FileModificationPair(url, revision))); set.staticRef(); m_index = set.setIndex(); } else { Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; Utils::Set tempSet = FileModificationSetRepositoryRepresenter::repository().createSet( fileModificationPairRepository().index(FileModificationPair(url, revision))); tempSet.staticRef(); newModificationTimes += tempSet; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); tempSet.staticUnref(); m_index = newModificationTimes.setIndex(); } } bool ModificationRevisionSet::removeModificationRevision(const IndexedString& url, const KDevelop::ModificationRevision& revision) { QMutexLocker lock(&modificationRevisionSetMutex); if (!m_index) return false; Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; Utils::Set tempSet = FileModificationSetRepositoryRepresenter::repository().createSet( fileModificationPairRepository().index(FileModificationPair(url, revision))); tempSet.staticRef(); newModificationTimes -= tempSet; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); tempSet.staticUnref(); m_index = newModificationTimes.setIndex(); return m_index != oldModificationTimes.setIndex(); } // const QMap ModificationRevisionSet::allModificationTimes() const { // QMap ret; // Utils::Set::Iterator it = m_allModificationTimes.iterator(); // while(it) { // const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); // ret[data->file] = data->revision; // ++it; // } // return ret; // } -typedef Utils::VirtualSetNode, - FileModificationSetRepositoryRepresenter> ModificationRevisionSetNode; +using ModificationRevisionSetNode = Utils::VirtualSetNode, + FileModificationSetRepositoryRepresenter>; // static bool (const Utils::SetNodeData* node) { // ModificationRevisionSetNode // if(!node) // return false; // } static bool nodeNeedsUpdate(uint index) { QMutexLocker lock(&modificationRevisionSetMutex); if (!index) return false; const auto currentTime = QDateTime::currentDateTime(); auto cached = needsUpdateCache.constFind(index); if (cached != needsUpdateCache.constEnd()) { if ((*cached).first.secsTo(currentTime) < cacheModificationTimesForSeconds) { return cached->second; } } bool result = false; const Utils::SetNodeData* nodeData = FileModificationSetRepositoryRepresenter::repository().nodeFromIndex(index); if (nodeData->contiguous()) { //Do the actual checking for (unsigned int a = nodeData->start(); a < nodeData->end(); ++a) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(a); ModificationRevision revision = KDevelop::ModificationRevision::revisionForFile(data->file); if (revision != data->revision) { result = true; break; } } } else { result = nodeNeedsUpdate(nodeData->leftNode()) || nodeNeedsUpdate(nodeData->rightNode()); } needsUpdateCache.insert(index, std::make_pair(currentTime, result)); return result; } QString ModificationRevisionSet::toString() const { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set::Iterator it = set.iterator(); QStringList revisions; while (it) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); revisions.append(data->file.str() + QLatin1Char(':') + data->revision.toString()); ++it; } QString ret = QLatin1Char('[') + revisions.join(QLatin1String(", ")) + QLatin1Char(']'); return ret; } bool ModificationRevisionSet::needsUpdate() const { QMutexLocker lock(&modificationRevisionSetMutex); #ifdef DEBUG_NEEDSUPDATE Utils::Set set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set::Iterator it = set.iterator(); while (it) { const FileModificationPair* data = fileModificationPairRepository().itemFromIndex(*it); ModificationRevision revision = KDevelop::ModificationRevision::revisionForFile(data->file); if (revision != data->revision) { qCDebug(LANGUAGE) << "dependency" << data->file.str() << "has changed, stored stamp:" << data->revision << "new time:" << revision; return true; } ++it; } return false; #else return nodeNeedsUpdate(m_index); #endif } ModificationRevisionSet& ModificationRevisionSet::operator+=(const ModificationRevisionSet& rhs) { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set otherModificationTimes = Utils::Set(rhs.m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; newModificationTimes += otherModificationTimes; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); m_index = newModificationTimes.setIndex(); return *this; } ModificationRevisionSet& ModificationRevisionSet::operator-=(const ModificationRevisionSet& rhs) { QMutexLocker lock(&modificationRevisionSetMutex); Utils::Set oldModificationTimes = Utils::Set(m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set otherModificationTimes = Utils::Set(rhs.m_index, &FileModificationSetRepositoryRepresenter::repository()); Utils::Set newModificationTimes = oldModificationTimes; newModificationTimes -= otherModificationTimes; newModificationTimes.staticRef(); oldModificationTimes.staticUnref(); m_index = newModificationTimes.setIndex(); return *this; } void FileModificationSetRepository::itemRemovedFromSets(uint index) { fileModificationPairRepository().deleteItem(index); needsUpdateCache.remove(index); } } diff --git a/kdevplatform/language/editor/persistentmovingrange.h b/kdevplatform/language/editor/persistentmovingrange.h index ebe335102f..43ecc456f7 100644 --- a/kdevplatform/language/editor/persistentmovingrange.h +++ b/kdevplatform/language/editor/persistentmovingrange.h @@ -1,95 +1,95 @@ /* Copyright 2010 David Nolden 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 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 KDEVPLATFORM_PERSISTENTMOVINGRANGE_H #define KDEVPLATFORM_PERSISTENTMOVINGRANGE_H #include #include #include #include namespace KDevelop { class IndexedString; class PersistentMovingRangePrivate; /** * A range object that is automatically adapted to all changes a user does to a document. The object * also survives when the document is opened or closed, as long as the document is only edited from within * the application. * * This object must only be used from within the foreground, or with the foreground lock held. * * @todo The implementation of this object is not finished yet, the range is only persistent until the * document is closed/reloaded/cleared. * */ class KDEVPLATFORMLANGUAGE_EXPORT PersistentMovingRange : public QSharedData { public: - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; /** * Creates a new persistent moving range based on the current revision of the given document * */ PersistentMovingRange(const KTextEditor::Range& range, const IndexedString& document, bool shouldExpand = false); ~PersistentMovingRange(); IndexedString document() const; /** * Returns the range in the current revision of the document */ KTextEditor::Range range() const; /** * Changes the z-depth for highlighting (see KTextEditor::MovingRange) * */ void setZDepth(float depth) const; /** * Returns the text contained by the range. Currently only works when the range is open in the editor. * */ QString text() const; /** * Change the highlighting attribute. * */ void setAttribute(const KTextEditor::Attribute::Ptr& attribute); /** * Whether this range is still valid. The range is invalidated if the document is changed externally, * as such a change can not be tracked correctly. * */ bool valid() const; private: PersistentMovingRange(const PersistentMovingRange&); PersistentMovingRange& operator=(const PersistentMovingRange& rhs); PersistentMovingRangePrivate* m_p; }; } #endif // KDEVPLATFORM_PERSISTENTMOVINGRANGE_H diff --git a/kdevplatform/language/highlighting/codehighlighting.h b/kdevplatform/language/highlighting/codehighlighting.h index 4727529018..d9c8215010 100644 --- a/kdevplatform/language/highlighting/codehighlighting.h +++ b/kdevplatform/language/highlighting/codehighlighting.h @@ -1,227 +1,227 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * 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 KDEVPLATFORM_CODEHIGHLIGHTING_H #define KDEVPLATFORM_CODEHIGHLIGHTING_H #include #include #include #include #include #include #include #include namespace KDevelop { class DUContext; class Declaration; -typedef QVector ColorMap; +using ColorMap = QVector; class CodeHighlighting; struct HighlightingEnumContainer { enum Types { UnknownType, //Primary highlighting: LocalClassMemberType, InheritedClassMemberType, LocalVariableType, //Other highlighting: ClassType, FunctionType, ForwardDeclarationType, EnumType, EnumeratorType, TypeAliasType, MacroType, /// Declaration of a macro such as "#define FOO" MacroFunctionLikeType, /// Declaration of a function like macro such as "#define FOO()" //If none of the above match: MemberVariableType, NamespaceVariableType, GlobalVariableType, //Most of these are currently not used: ArgumentType, CodeType, FileType, NamespaceType, ScopeType, TemplateType, TemplateParameterType, FunctionVariableType, ErrorVariableType }; enum Contexts { DefinitionContext, DeclarationContext, ReferenceContext }; }; struct HighlightedRange { RangeInRevision range; KTextEditor::Attribute::Ptr attribute; bool operator<(const HighlightedRange& rhs) const { return range.start < rhs.range.start; } }; /** * Code highlighting instance that is used to apply code highlighting to one specific top context * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlightingInstance : public HighlightingEnumContainer { public: explicit CodeHighlightingInstance(const CodeHighlighting* highlighting) : m_useClassCache(false) , m_highlighting(highlighting) { } virtual ~CodeHighlightingInstance() { } virtual void highlightDeclaration(KDevelop::Declaration* declaration, const QColor& color); virtual void highlightUse(KDevelop::DUContext* context, int index, const QColor& color); virtual void highlightUses(KDevelop::DUContext* context); void highlightDUChain(KDevelop::TopDUContext* context); void highlightDUChain(KDevelop::DUContext* context, QHash colorsForDeclarations, ColorMap); KDevelop::Declaration* localClassFromCodeContext(KDevelop::DUContext* context) const; /** * @param context Should be the context from where the declaration is used, if a use is highlighted. * */ virtual Types typeForDeclaration(KDevelop::Declaration* dec, KDevelop::DUContext* context) const; /** * Decides whether to apply auto-generated rainbow colors to @p dec. * Default implementation only applies that to local variables in functions. */ virtual bool useRainbowColor(KDevelop::Declaration* dec) const; //A temporary hash for speedup mutable QHash m_contextClasses; //Here the colors of function context are stored until they are merged into the function body mutable QMap> m_functionColorsForDeclarations; mutable QMap m_functionDeclarationsForColors; mutable bool m_useClassCache; const CodeHighlighting* m_highlighting; QVector m_highlight; }; /** * General class representing the code highlighting for one language * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlighting : public QObject , public KDevelop::ICodeHighlighting , public HighlightingEnumContainer { Q_OBJECT Q_INTERFACES(KDevelop::ICodeHighlighting) public: explicit CodeHighlighting(QObject* parent); ~CodeHighlighting() override; /// This function is thread-safe /// @warning The duchain must not be locked when this is called (->possible deadlock) void highlightDUChain(ReferencedTopDUContext context) override; //color should be zero when undecided KTextEditor::Attribute::Ptr attributeForType(Types type, Contexts context, const QColor& color) const; KTextEditor::Attribute::Ptr attributeForDepth(int depth) const; /// This function is thread-safe /// Returns whether a highlighting is already given for the given url bool hasHighlighting(IndexedString url) const override; private: //Returns whether the given attribute was set by the code highlighting, and not by something else //Always returns true when the attribute is zero bool isCodeHighlight(KTextEditor::Attribute::Ptr attr) const; protected: //Can be overridden to create an own instance type virtual CodeHighlightingInstance* createInstance() const; private: /// Highlighting of one specific document struct DocumentHighlighting { IndexedString m_document; qint64 m_waitingRevision; // The ranges are sorted by range start, so they can easily be matched QVector m_waiting; QVector m_highlightedRanges; }; QMap m_highlights; friend class CodeHighlightingInstance; mutable QHash m_definitionAttributes; mutable QHash m_declarationAttributes; mutable QHash m_referenceAttributes; mutable QList m_depthAttributes; // Should be used to enable/disable the colorization of local variables and their uses bool m_localColorization; // Should be used to enable/disable the colorization of global types and their uses bool m_globalColorization; mutable QMutex m_dataMutex; private Q_SLOTS: void clearHighlightingForDocument(const KDevelop::IndexedString& document); void applyHighlighting(void* highlighting); void trackerDestroyed(QObject* object); /// when the colors change we must invalidate our local caches void adaptToColorChanges(); void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*); void aboutToRemoveText(const KTextEditor::Range&); }; } Q_DECLARE_TYPEINFO(KDevelop::HighlightedRange, Q_MOVABLE_TYPE); #endif diff --git a/kdevplatform/language/interfaces/iastcontainer.h b/kdevplatform/language/interfaces/iastcontainer.h index 9fc48dd501..b9e5fca719 100644 --- a/kdevplatform/language/interfaces/iastcontainer.h +++ b/kdevplatform/language/interfaces/iastcontainer.h @@ -1,48 +1,48 @@ /* Copyright 2009 Ramón Zarazúa 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 KDEVPLATFORM_IASTCONTAINER_H #define KDEVPLATFORM_IASTCONTAINER_H #include #include namespace KDevelop { class COMPILE_ERROR_Ast_Top_Node_Was_Not_Defined; /** * \brief Represents A single parse session that created a language-specific AST for a single translation unit * * This class is mainly just used as a tag to keep pointers to an AST of a translation unit(file) * in a KDevelop::TopDUContext, and passed over to Refactoring plugins so they can manipulate * their language-specific AstChangeSets. */ class KDEVPLATFORMLANGUAGE_EXPORT IAstContainer : public QSharedData { public: virtual ~IAstContainer(); - typedef QExplicitlySharedDataPointer Ptr; + using Ptr = QExplicitlySharedDataPointer; /// Derived classes must redifine this typedef for CodeGenerator to work /// with this AST - typedef COMPILE_ERROR_Ast_Top_Node_Was_Not_Defined TopAstNode; + using TopAstNode = COMPILE_ERROR_Ast_Top_Node_Was_Not_Defined; }; } #endif // KDEVPLATFORM_IASTCONTAINER_H diff --git a/kdevplatform/language/interfaces/quickopendataprovider.h b/kdevplatform/language/interfaces/quickopendataprovider.h index 0d87c0cf50..f93b2770b4 100644 --- a/kdevplatform/language/interfaces/quickopendataprovider.h +++ b/kdevplatform/language/interfaces/quickopendataprovider.h @@ -1,211 +1,211 @@ /* * This file is part of KDevelop * * Copyright 2007 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 KDEVPLATFORM_QUICKOPENDATAPROVIDER_H #define KDEVPLATFORM_QUICKOPENDATAPROVIDER_H #include #include #include #include #include class QString; class QStringList; class QIcon; namespace KDevelop { class IndexedString; /** * Hint: When implementing a data-provider, do not forget to export it! Else it won't work. * */ /** * If your plugin manages a list of files, you can use this to return that list. * The file-list can be queried by other data-providers(for example functions/methods) so they * can manipulate their content based on those file-lists. The file-list should not be filtered at all, * it should only depend on the enabled models/items * * Example: A list of files in the include-path, a list of files in the project, etc. * */ class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenFileSetInterface { public: virtual QSet files() const = 0; virtual ~QuickOpenFileSetInterface(); }; /** * You can use this as additional base-class for your embedded widgets to get additional interaction * */ class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenEmbeddedWidgetInterface { public: virtual ~QuickOpenEmbeddedWidgetInterface(); ///Is called when the keyboard-shortcut "next" is triggered on the widget, which currently is SHIFT+Right virtual void next() = 0; ///Is called when the keyboard-shortcut "previous" is triggered on the widget, which currently is SHIFT+Left virtual void previous() = 0; ///Is called when the keyboard-shortcut "accept" is triggered on the widget, which currently is SHIFT+Return virtual void accept() = 0; ///Is called when the keyboard-shortcut "scroll up" is triggered on the widget, which currently is SHIFT+Up virtual void up() = 0; ///Is called when the keyboard-shortcut "scroll down" is triggered on the widget, which currently is SHIFT+Down virtual void down() = 0; }; /** * Reimplement this to represent single entries within the quickopen list. * */ class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenDataBase : public QSharedData { public: virtual ~QuickOpenDataBase(); ///Return the text to be shown in the list for this item virtual QString text() const = 0; virtual QString htmlDescription() const = 0; /**Can return Custom highlighting triplets as explained in * the kde header ktexteditor/codecompletionmodel.h * The default-implementation returns an empty list, which means no * special highlighting will be applied. * */ virtual QList highlighting() const; /** * May return an icon to mark the item in the quickopen-list. * The standard-implementation returns an invalid item, which means that * no icon will be shown. * */ virtual QIcon icon() const; /** * Is called when the item should be executed. * * @param filterText Current content of the quickopen-dialogs filter line-edit. * If this is changed, and false is returned, the content of the * line-edit will be changed according to the new text. * @return Whether the dialog should be closed. * */ virtual bool execute(QString& filterText) = 0; /** * Return true here if this data-item should be expandable with * an own embedded widget. * The default-implementation returns false. * */ virtual bool isExpandable() const; /** * This will be called if isExpandable() returns true. * * A widget should be returned that will be embedded into the quickopen-list. * The widget will be owned by the quickopen-list and will be deleted at will. * * If the widget can be dynamic_cast'ed to QuickOpenEmbeddedWidgetInterface, * the additional interaction defined there will be possible. * * The default-implementation returns 0, which means no widget will be shown. * */ virtual QWidget* expandingWidget() const; }; -typedef QExplicitlySharedDataPointer QuickOpenDataPointer; +using QuickOpenDataPointer = QExplicitlySharedDataPointer; /** * Use this interface to provide custom quickopen-data to the quickopen-widget. * * If possible, you should use KDevelop::Filter (@file quickopenfilter.h ) * to implement the actual filtering, so it is consistent. * */ class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenDataProviderBase : public QObject { Q_OBJECT public: ~QuickOpenDataProviderBase() override; /** * For efficiency, all changes to the filter-text are provided by the following 3 difference-operations. * */ /** * Search-text was changed. * This is called whenever the search-text was changed, and the UI should be updated. * Store the text to track the exact difference. * */ virtual void setFilterText(const QString& text) = 0; /** * Filter-text should be completely reset and the context re-computed. * */ virtual void reset() = 0; /** * Returns the count of items this provider currently represents * */ virtual uint itemCount() const = 0; /** * Returns the count of *unfiltered* items this provider currently represents */ virtual uint unfilteredItemCount() const = 0; /** * Returns the data-item for a given row. * * Generally, the items must addressed alphabetically, * they will be displayed in the same order in the * quickopen list. * * For performance-reasons the underlying models should * create the QuickOpenDataBase items on demand, because only * those that will really be shown will be requested. * * @param row Index of item to be returned. * */ virtual QuickOpenDataPointer data(uint row) const = 0; /** * If the data-provider supports multiple different scopes/items, this will be called * with the enabled scopes/items. * If the data-provider supports only one scope/item, this can be ignored. * The lists contains all scopes/items, even those that are not supported by this provider. * */ virtual void enableData(const QStringList& items, const QStringList& scopes); }; /** * Try parsing string according to "path_to_file":"line number" template. "line number" may be empty. * @param from Source string * @param path Set to parsed path to file, or left unchanged if @p from doesn't match the template. May refer to the same object as @p from * @param lineNumber Set to parsed line number, zero if "line number" is empty or left unchanged if @p from doesn't match the template. * @return Whether @p from did match the expected template. * */ bool KDEVPLATFORMLANGUAGE_EXPORT extractLineNumber(const QString& from, QString& path, uint& lineNumber); } #endif diff --git a/kdevplatform/language/util/basicsetrepository.h b/kdevplatform/language/util/basicsetrepository.h index eeb26ba1f5..c23e55162e 100644 --- a/kdevplatform/language/util/basicsetrepository.h +++ b/kdevplatform/language/util/basicsetrepository.h @@ -1,376 +1,376 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_BASICSETREPOSITORY_H #define KDEVPLATFORM_BASICSETREPOSITORY_H #include #include #include #include #include /** * This file provides a set system that can be used to efficiently manage sub-sets of a set of global objects. * Each global object must be mapped to a natural number. * * The efficiency comes from: * 1. The sets are represented by binary trees, where every single tree node can be shared across multiple sets. * For that reason, intersecting sets will share large parts of their internal data structures, which leads to * extremely low memory-usage compared to using for example std::set. * * The more common intersections between the sets exist, the more efficient the system is. * This will have the biggest advantage when items that were added contiguously are commonly * used within the same sets, and work fastest if the intersections between different sets are contiguously long. * * That makes it perfect for representing sets that are inherited across tree-like structures, like for example in C++: * - Macros defined in files(Macros are inherited from included files) * - Strings contained in files(Strings are inherited from included files) * - Set of all included files * * Measurements(see in kdevelop languages/cpp/cppduchain/tests/duchaintest) show that even in worst case(with totally random sets) * these set-repositories are 2 times faster than std::set, and 4 times faster than QSet. * * The main disadvantages are that a global repository needs to be managed, and needs to be secured from simultaneous write-access * during multi-threading. This is done internally if the doLocking flag is set while constructing. * */ class QString; namespace Utils { enum { delayedDeletionByDefault = 0 }; class SetNode; class BasicSetRepository; class SetNodeDataRequest; ///Internal node representation, exported here for performance reason. struct KDEVPLATFORMLANGUAGE_EXPORT SetNodeData { private: //Rule: start < end uint m_start, m_end; //This set-node bounds all indices starting at start until end, not including end. //Child nodes //Rule: left->start == start, right->end == end //Rule: (left != 0 && right != 0) || (left == 0 && right == 0) uint m_leftNode, m_rightNode; // cached hash of this node data - it only includes the first four data members, // i.e. m_start, m_end, m_leftNode and m_rightNode uint m_hash; public: uint m_refCount = 0; inline explicit SetNodeData(uint start = 1, uint end = 1, uint leftNode = 0, uint rightNode = 0) : m_start(start) , m_end(end) , m_leftNode(leftNode) , m_rightNode(rightNode) , m_hash(calculateHash()) { } inline uint hash() const { return m_hash; } uint calculateHash() const { return KDevHash() << m_start << m_end << m_leftNode << m_rightNode; } inline short unsigned int itemSize() const { return sizeof(SetNodeData); } inline bool contiguous() const { return !m_leftNode; } inline bool hasSlaves() const { return ( bool )m_leftNode; } inline uint start() const { return m_start; } inline uint end() const { return m_end; } inline uint leftNode() const { return m_leftNode; } inline uint rightNode() const { return m_rightNode; } }; -typedef KDevelop::ItemRepository SetDataRepositoryBase; +using SetDataRepositoryBase = + KDevelop::ItemRepository; struct SetDataRepository; class SetNodeDataRequest { public: enum { AverageSize = sizeof(SetNodeData) }; //This constructor creates a request that finds or creates a node that equals the given node //The m_hash must be up to date, and the node must be split correctly around its splitPosition inline SetNodeDataRequest(const SetNodeData* _data, SetDataRepository& _repository, BasicSetRepository* _setRepository); ~SetNodeDataRequest(); - typedef unsigned int HashType; + using HashType = unsigned int; //Should return the m_hash-value associated with this request(For example the m_hash of a string) inline HashType hash() const { return m_hash; } //Should return the size of an item created with createItem inline uint itemSize() const { return sizeof(SetNodeData); } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(SetNodeData* item) const; static void destroy(SetNodeData* data, KDevelop::AbstractItemRepository& _repository); static bool persistent(const SetNodeData* item) { return ( bool )item->m_refCount; } //Should return whether the here requested item equals the given item bool equals(const SetNodeData* item) const; SetNodeData data; uint m_hash; SetDataRepository& repository; mutable BasicSetRepository* setRepository; //May be zero when no notifications are wanted mutable bool m_created; }; struct KDEVPLATFORMLANGUAGE_EXPORT SetDataRepository : public SetDataRepositoryBase { SetDataRepository(BasicSetRepository* _setRepository, const QString& name, KDevelop::ItemRepositoryRegistry* registry) : SetDataRepositoryBase(name, registry) , setRepository(_setRepository) { } BasicSetRepository* setRepository; }; /** * This object is copyable. It represents a set, and allows iterating through the represented indices. * */ class KDEVPLATFORMLANGUAGE_EXPORT Set { public: class Iterator; - typedef unsigned int Index; + using Index = unsigned int; Set(); //Internal constructor Set(uint treeNode, BasicSetRepository* repository); ~Set(); Set(const Set& rhs); Set& operator=(const Set& rhs); QString dumpDotGraph() const; //Returns an itrator that can be used to iterate over the contained indices Iterator iterator() const; //Returns this set converted to a standard set that contains all indices contained by this set. std::set stdSet() const; ///Returns the count of items in the set unsigned int count() const; bool contains(Index index) const; ///@warning: The following operations can change the global repository, and thus need to be serialized /// using mutexes in case of multi-threading. ///Set union Set operator +(const Set& rhs) const; Set& operator +=(const Set& rhs); ///Set intersection Set operator &(const Set& rhs) const; Set& operator &=(const Set& rhs); ///Set subtraction Set operator -(const Set& rhs) const; Set& operator -=(const Set& rhs); uint setIndex() const { return m_tree; } ///Increase the static reference-count of this set by one. The initial reference-count of newly created sets is zero. void staticRef(); ///Decrease the static reference-count of this set by one. This set must have a reference-count >= 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void staticUnref(); ///Returns a pointer to the repository this set belongs to. Returns zero when this set is not initialized yet. BasicSetRepository* repository() const; private: void unrefNode(uint); friend class BasicSetRepository; uint m_tree = 0; mutable BasicSetRepository* m_repository = nullptr; }; /** * This is a repository that can be used to efficiently manage generic sets * that are represented by interweaved binary trees. * * All strings are based on items that are contained in one master-repository, * starting at one. * * An index of zero is interpreted as invalid. * */ class KDEVPLATFORMLANGUAGE_EXPORT BasicSetRepository { public: ///@param name The name must be unique, and is used for loading and storing the data ///@param registry Where the repository should be registered. If you give zero, it won't be registered, and thus won't be saved to disk. explicit BasicSetRepository(const QString& name, KDevelop::ItemRepositoryRegistry* registry = & KDevelop::globalItemRepositoryRegistry(), bool delayedDeletion = delayedDeletionByDefault); virtual ~BasicSetRepository(); - typedef unsigned int Index; + using Index = unsigned int; /** * Takes a sorted list indices, returns a set representing them * */ Set createSetFromIndices(const std::vector& indices); /** * Takes a simple set of indices * */ Set createSet(const std::set& indices); /** * Creates a set that only contains that single index. * For better performance, you should create bigger sets than this. * */ Set createSet(Index i); void printStatistics() const; ///Is called when this index is not part of any set any more virtual void itemRemovedFromSets(uint index); ///Is called when this index is added to one of the contained sets for the first time virtual void itemAddedToSets(uint index); inline const SetNodeData* nodeFromIndex(uint index) const { if (index) return m_dataRepository.itemFromIndex(index); else return nullptr; } inline QMutex* mutex() const { return m_mutex; } ///Only public to get statistics and such const SetDataRepository& dataRepository() const { return m_dataRepository; } ///Set whether set-nodes with reference-count zero should be deleted only after a delay ///The default is true. ///This may be faster when the structure is large anyway and many temporary sets ///are created, but leads to a sparse structure in memory, which is bad for cache. void setDelayedDeletion(bool delayed) { m_delayedDeletion = delayed; } inline bool delayedDeletion() const { return m_delayedDeletion; } private: friend class Set; friend class Set::Iterator; SetDataRepository m_dataRepository; QMutex* m_mutex; bool m_delayedDeletion; // SetNode }; /** * Use this to iterate over the indices contained in a set * */ class KDEVPLATFORMLANGUAGE_EXPORT Set::Iterator { public: Iterator(); Iterator(const Iterator& rhs); Iterator& operator=(const Iterator& rhs); ~Iterator(); operator bool() const; Iterator& operator++(); BasicSetRepository::Index operator*() const; private: friend class Set; friend class SetIteratorPrivate; static inline SetDataRepository& getDataRepository(BasicSetRepository* repo) { return repo->m_dataRepository; } const QScopedPointer d; }; } #endif diff --git a/kdevplatform/language/util/debuglanguageparserhelper.h b/kdevplatform/language/util/debuglanguageparserhelper.h index 05cb616af2..9ebdfc73db 100644 --- a/kdevplatform/language/util/debuglanguageparserhelper.h +++ b/kdevplatform/language/util/debuglanguageparserhelper.h @@ -1,245 +1,245 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff Copyright 2014 Kevin Funk 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 KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H #define KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H #include #include #ifndef Q_OS_WIN #include // for isatty #endif #include #include #include #include #include #include #include #include #include #include #include namespace KDevelopUtils { QTextStream qout(stdout); QTextStream qerr(stderr); QTextStream qin(stdin); -typedef QString (* TokenTextFunc)(int); +using TokenTextFunc = QString (*)(int); /** * This class is a pure helper to use for binaries that you can * run on short snippets of test code or whole files and let * it print the generated tokens or AST. * * It should work fine for any KDevelop-PG-Qt based parser. * * * @tparam SessionT the parse session for your language. * @tparam TokenStreamT the token stream for your language, based on KDevPG::TokenStreamBase. * @tparam TokenT the token class for your language, based on KDevPG::Token. * @tparam LexerT the Lexer for your language. * @tparam StartAstT the AST node that is returned from @c SessionT::parse(). * @tparam DebugVisitorT the debug visitor for your language. * @tparam TokenTextT function pointer to the function that returns a string representation for an integral token. */ template class DebugLanguageParserHelper { public: DebugLanguageParserHelper(const bool printAst, const bool printTokens) : m_printAst(printAst) , m_printTokens(printTokens) { m_session.setDebug(printAst); } /// parse contents of a file void parseFile(const QString& fileName) { if (!m_session.readFile(fileName, "utf-8")) { qerr << "Can't open file " << fileName << endl; std::exit(255); } else { qout << "Parsing file " << fileName << endl; } runSession(); } /// parse code directly void parseCode(const QString& code) { m_session.setContents(code); qout << "Parsing input" << endl; runSession(); } private: /** * actually run the parse session */ void runSession() { if (m_printTokens) { TokenStreamT tokenStream; LexerT lexer(&tokenStream, m_session.contents()); int token; while ((token = lexer.nextTokenKind())) { TokenT& t = tokenStream.push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = token; printToken(token, lexer); } printToken(token, lexer); if (tokenStream.size() > 0) { qint64 line; qint64 column; tokenStream.endPosition(tokenStream.size() - 1, &line, &column); qDebug() << "last token endPosition: line" << line << "column" << column; } else { qDebug() << "empty token stream"; } } StartAstT* ast = 0; if (!m_session.parse(&ast)) { qerr << "no AST tree could be generated" << endl; } else { qout << "AST tree successfully generated" << endl; if (m_printAst) { DebugVisitorT debugVisitor(m_session.tokenStream(), m_session.contents()); debugVisitor.visitStart(ast); } } if (!m_session.problems().isEmpty()) { qout << endl << "problems encountered during parsing:" << endl; foreach (KDevelop::ProblemPointer p, m_session.problems()) { qout << p->description() << endl; } } else { qout << "no problems encountered during parsing" << endl; } if (!ast) { exit(255); } } void printToken(int token, const LexerT& lexer) const { int begin = lexer.tokenBegin(); int end = lexer.tokenEnd(); qout << m_session.contents().mid(begin, end - begin + 1).replace('\n', "\\n") << ' ' << TokenTextT(token) << endl; } SessionT m_session; const bool m_printAst; const bool m_printTokens; }; template void setupCustomArgs(QCommandLineParser* parser) { Q_UNUSED(parser); } template void setCustomArgs(ParserT* parser, QCommandLineParser* commandLineParser) { Q_UNUSED(parser); Q_UNUSED(commandLineParser); } /// call this after setting up @p aboutData in your @c main() function. template int initAndRunParser(KAboutData& aboutData, int argc, char* argv[]) { qout.setCodec("UTF-8"); qerr.setCodec("UTF-8"); qin.setCodec("UTF-8"); QApplication app(argc, argv); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addPositionalArgument("files", i18n( "files or - to read from STDIN, the latter is the default if nothing is provided"), "[FILE...]"); parser.addOption(QCommandLineOption{QStringList{"a", "print-ast"}, i18n("print generated AST tree")}); parser.addOption(QCommandLineOption{QStringList{"t", "print-tokens"}, i18n("print generated token stream")}); parser.addOption(QCommandLineOption{QStringList{"c", "code"}, i18n("code to parse"), "code"}); setupCustomArgs(&parser); parser.process(app); aboutData.processCommandLine(&parser); QStringList files = parser.positionalArguments(); bool printAst = parser.isSet("print-ast"); bool printTokens = parser.isSet("print-tokens"); KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); KDevelop::DUChain::self()->disablePersistentStorage(); KDevelop::CodeRepresentation::setDiskChangesForbidden(true); ParserT parserT(printAst, printTokens); setCustomArgs(&parserT, &parser); if (parser.isSet("code")) { parserT.parseCode(parser.value("code")); } else if (files.isEmpty()) { files << "-"; } foreach (const QString& fileName, files) { if (fileName == "-") { #ifndef Q_OS_WIN if (isatty(STDIN_FILENO)) { qerr << "no STDIN given" << endl; return 255; } #endif parserT.parseCode(qin.readAll().toUtf8()); } else { parserT.parseFile(fileName); } } KDevelop::TestCore::shutdown(); return 0; } } #endif // KDEVPLATFORM_DEBUGLANGUAGEPARSERHELPER_H diff --git a/kdevplatform/language/util/setrepository.cpp b/kdevplatform/language/util/setrepository.cpp index 86e721a036..d305ad7418 100644 --- a/kdevplatform/language/util/setrepository.cpp +++ b/kdevplatform/language/util/setrepository.cpp @@ -1,1202 +1,1202 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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 "setrepository.h" #include #include #include #include #include #include #include #include #include #include //#define DEBUG_SETREPOSITORY #ifdef DEBUG_SETREPOSITORY #define ifDebug(X) X #else #define ifDebug(x) #undef Q_ASSERT #define Q_ASSERT(x) #endif #ifndef DEBUG_SETREPOSITORY #define CHECK_SPLIT_POSITION(Node) #else #define CHECK_SPLIT_POSITION(node) Q_ASSERT(!(node).leftNode || \ (getLeftNode(&node)->end() <= \ splitPositionForRange((node).start, \ (node).end) && \ getRightNode(&node)->start() >= \ splitPositionForRange((node).start, (node).end))) #endif namespace Utils { /** * To achieve a maximum re-usage of nodes, we make sure that sub-nodes of a node always split at specific boundaries. * For each range we can compute a position where that range should be split into its child-nodes. * When creating a new node with 2 sub-nodes, we re-create those child-nodes if their boundaries don't represent those split-positions. * * We pick the split-positions deterministically, they are in order of priority: * ((1<<31)*n, n = [0,...] * ((1<<30)*n, n = [0,...] * ((1<<29)*n, n = [0,...] * ((1<<...)*n, n = [0,...] * ... * */ -typedef BasicSetRepository::Index Index; +using Index = BasicSetRepository::Index; ///The returned split position shall be the end of the first sub-range, and the start of the second ///@param splitBit should be initialized with 31, unless you know better. The value can then be used on while computing child split positions. ///In the end, it will contain the bit used to split the range. It will also contain zero if no split-position exists(length 1) uint splitPositionForRange(uint start, uint end, uchar& splitBit) { if (end - start == 1) { splitBit = 0; return 0; } while (true) { uint position = ((end - 1) >> splitBit) << splitBit; //Round to the split-position in this interval that is smaller than end if (position > start && position < end) return position; Q_ASSERT(splitBit != 0); --splitBit; } return 0; } uint splitPositionForRange(uint start, uint end) { uchar splitBit = 31; return splitPositionForRange(start, end, splitBit); } class SetNodeDataRequest; #define getLeftNode(node) repository.itemFromIndex(node->leftNode()) #define getRightNode(node) repository.itemFromIndex(node->rightNode()) #define nodeFromIndex(index) repository.itemFromIndex(index) struct SetRepositoryAlgorithms { SetRepositoryAlgorithms(SetDataRepository& _repository, BasicSetRepository* _setRepository) : repository(_repository) , setRepository(_setRepository) { } ///Expensive Index count(const SetNodeData* node) const; void localCheck(const SetNodeData* node); void check(uint node); void check(const SetNodeData* node); QString shortLabel(const SetNodeData& node) const; uint set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); uint createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left = nullptr, const SetNodeData* right = nullptr); uint computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit); uint set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); bool set_contains(const SetNodeData* node, Index index); uint set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit = 31); //Required both nodes to be split correctly bool set_equals(const SetNodeData* lhs, const SetNodeData* rhs); QString dumpDotGraph(uint node) const; ///Finds or inserts the given ranges into the repository, and returns the set-index that represents them uint setForIndices(std::vector::const_iterator begin, std::vector::const_iterator end, uchar splitBit = 31) { Q_ASSERT(begin != end); uint startIndex = *begin; uint endIndex = *(end - 1) + 1; if (endIndex == startIndex + 1) { SetNodeData data(startIndex, endIndex); return repository.index(SetNodeDataRequest(&data, repository, setRepository)); } uint split = splitPositionForRange(startIndex, endIndex, splitBit); Q_ASSERT(split); auto splitIterator = std::lower_bound(begin, end, split); Q_ASSERT(*splitIterator >= split); Q_ASSERT(splitIterator > begin); Q_ASSERT(*(splitIterator - 1) < split); return createSetFromNodes(setForIndices(begin, splitIterator, splitBit), setForIndices(splitIterator, end, splitBit)); } private: QString dumpDotGraphInternal(uint node, bool master = false) const; SetDataRepository& repository; BasicSetRepository* setRepository; }; void SetNodeDataRequest::destroy(SetNodeData* data, KDevelop::AbstractItemRepository& _repository) { auto& repository(static_cast(_repository)); if (repository.setRepository->delayedDeletion()) { if (data->leftNode()) { SetDataRepositoryBase::MyDynamicItem left = repository.dynamicItemFromIndex(data->leftNode()); SetDataRepositoryBase::MyDynamicItem right = repository.dynamicItemFromIndex(data->rightNode()); Q_ASSERT(left->m_refCount > 0); --left->m_refCount; Q_ASSERT(right->m_refCount > 0); --right->m_refCount; } else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); repository.setRepository->itemRemovedFromSets(data->start()); } } } SetNodeDataRequest::SetNodeDataRequest(const SetNodeData* _data, SetDataRepository& _repository, BasicSetRepository* _setRepository) : data(*_data) , m_hash(_data->hash()) , repository(_repository) , setRepository(_setRepository) , m_created(false) { ifDebug(SetRepositoryAlgorithms alg(repository); alg.check(_data)); } SetNodeDataRequest::~SetNodeDataRequest() { //Eventually increase the reference-count of direct children if (m_created) { if (data.leftNode()) ++repository.dynamicItemFromIndex(data.leftNode())->m_refCount; if (data.rightNode()) ++repository.dynamicItemFromIndex(data.rightNode())->m_refCount; } } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void SetNodeDataRequest::createItem(SetNodeData* item) const { Q_ASSERT((data.rightNode() && data.leftNode()) || (!data.rightNode() && !data.leftNode())); m_created = true; *item = data; Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); #ifdef DEBUG_SETREPOSITORY //Make sure we split at the correct split position if (item->hasSlaves()) { uint split = splitPositionForRange(data.start, data.end); const SetNodeData* left = repository.itemFromIndex(item->leftNode()); const SetNodeData* right = repository.itemFromIndex(item->rightNode()); Q_ASSERT(split >= left->end() && split <= right->start()); } #endif if (!data.leftNode() && setRepository) { for (uint a = item->start(); a < item->end(); ++a) setRepository->itemAddedToSets(a); } } bool SetNodeDataRequest::equals(const SetNodeData* item) const { Q_ASSERT((item->rightNode() && item->leftNode()) || (!item->rightNode() && !item->leftNode())); //Just compare child nodes, since data must be correctly split, this is perfectly ok //Since this happens in very tight loops, we don't call an additional function here, but just do the check. return item->leftNode() == data.leftNode() && item->rightNode() == data.rightNode() && item->start() == data.start() && item->end() == data.end(); } Set::Set() { } Set::~Set() { } unsigned int Set::count() const { if (!m_repository || !m_tree) return 0; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); return alg.count(m_repository->m_dataRepository.itemFromIndex(m_tree)); } Set::Set(uint treeNode, BasicSetRepository* repository) : m_tree(treeNode) , m_repository(repository) { } Set::Set(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; } Set& Set::operator=(const Set& rhs) { m_repository = rhs.m_repository; m_tree = rhs.m_tree; return *this; } QString Set::dumpDotGraph() const { if (!m_repository || !m_tree) return QString(); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); return alg.dumpDotGraph(m_tree); } Index SetRepositoryAlgorithms::count(const SetNodeData* node) const { if (node->leftNode() && node->rightNode()) return count(getLeftNode(node)) + count(getRightNode(node)); else return node->end() - node->start(); } void SetRepositoryAlgorithms::localCheck(const SetNodeData* ifDebug(node)) { // Q_ASSERT(node->start() > 0); Q_ASSERT(node->start() < node->end()); Q_ASSERT((node->leftNode() && node->rightNode()) || (!node->leftNode() && !node->rightNode())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->start() == node->start() && getRightNode(node)->end() == node->end())); Q_ASSERT(!node->leftNode() || (getLeftNode(node())->end() <= getRightNode(node)->start())); } void SetRepositoryAlgorithms::check(uint node) { if (!node) return; check(nodeFromIndex(node)); } void SetRepositoryAlgorithms::check(const SetNodeData* node) { localCheck(node); if (node->leftNode()) check(getLeftNode(node)); if (node->rightNode()) check(getRightNode(node)); // CHECK_SPLIT_POSITION(*node); Re-enable this } QString SetRepositoryAlgorithms::shortLabel(const SetNodeData& node) const { return QStringLiteral("n%1_%2").arg(node.start()).arg(node.end()); } QString SetRepositoryAlgorithms::dumpDotGraphInternal(uint nodeIndex, bool master) const { if (!nodeIndex) return QStringLiteral("empty node"); const SetNodeData& node(*repository.itemFromIndex(nodeIndex)); QString color = QStringLiteral("blue"); if (master) color = QStringLiteral("red"); QString label = QStringLiteral("%1 -> %2").arg(node.start()).arg(node.end()); if (!node.contiguous()) label += QLatin1String(", with gaps"); QString ret = QStringLiteral("%1[label=\"%2\", color=\"%3\"];\n").arg(shortLabel(node), label, color); if (node.leftNode()) { const SetNodeData& left(*repository.itemFromIndex(node.leftNode())); const SetNodeData& right(*repository.itemFromIndex(node.rightNode())); Q_ASSERT(node.rightNode()); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(left)); ret += QStringLiteral("%1 -> %2;\n").arg(shortLabel(node), shortLabel(right)); ret += dumpDotGraphInternal(node.leftNode()); ret += dumpDotGraphInternal(node.rightNode()); } return ret; } QString SetRepositoryAlgorithms::dumpDotGraph(uint nodeIndex) const { QString ret = QStringLiteral("digraph Repository {\n"); ret += dumpDotGraphInternal(nodeIndex, true); ret += QLatin1String("}\n"); return ret; } const int nodeStackAlloc = 500; class SetIteratorPrivate { public: SetIteratorPrivate() { nodeStackData.resize(nodeStackAlloc); nodeStack = nodeStackData.data(); } SetIteratorPrivate(const SetIteratorPrivate& rhs) : nodeStackData(rhs.nodeStackData) , nodeStackSize(rhs.nodeStackSize) , currentIndex(rhs.currentIndex) , repository(rhs.repository) { nodeStack = nodeStackData.data(); } SetIteratorPrivate& operator=(const SetIteratorPrivate& rhs) { nodeStackData = rhs.nodeStackData; nodeStackSize = rhs.nodeStackSize; currentIndex = rhs.currentIndex; repository = rhs.repository; nodeStack = nodeStackData.data(); return *this; } void resizeNodeStack() { nodeStackData.resize(nodeStackSize + 1); nodeStack = nodeStackData.data(); } KDevVarLengthArray nodeStackData; const SetNodeData** nodeStack; int nodeStackSize = 0; Index currentIndex = 0; BasicSetRepository* repository = nullptr; /** * Pushes the noed on top of the stack, changes currentIndex, and goes as deep as necessary for iteration. * */ void startAtNode(const SetNodeData* node) { Q_ASSERT(node->start() != node->end()); currentIndex = node->start(); do { nodeStack[nodeStackSize++] = node; if (nodeStackSize >= nodeStackAlloc) resizeNodeStack(); if (node->contiguous()) break; //We need no finer granularity, because the range is contiguous node = Set::Iterator::getDataRepository(repository).itemFromIndex(node->leftNode()); } while (node); Q_ASSERT(currentIndex >= nodeStack[0]->start()); } }; std::set Set::stdSet() const { Set::Iterator it = iterator(); std::set ret; while (it) { Q_ASSERT(ret.find(*it) == ret.end()); ret.insert(*it); ++it; } return ret; } Set::Iterator::Iterator(const Iterator& rhs) : d(new SetIteratorPrivate(*rhs.d)) { } Set::Iterator& Set::Iterator::operator=(const Iterator& rhs) { *d = *rhs.d; return *this; } Set::Iterator::Iterator() : d(new SetIteratorPrivate) { } Set::Iterator::~Iterator() = default; Set::Iterator::operator bool() const { return d->nodeStackSize; } Set::Iterator& Set::Iterator::operator++() { Q_ASSERT(d->nodeStackSize); if (d->repository->m_mutex) d->repository->m_mutex->lock(); ++d->currentIndex; //const SetNodeData** currentNode = &d->nodeStack[d->nodeStackSize - 1]; if (d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { //Advance to the next node while (d->nodeStackSize && d->currentIndex >= d->nodeStack[d->nodeStackSize - 1]->end()) { --d->nodeStackSize; } if (!d->nodeStackSize) { //ready } else { //++d->nodeStackSize; //We were iterating the left slave of the node, now continue with the right. ifDebug(const SetNodeData& left = *d->repository->m_dataRepository.itemFromIndex( d->nodeStack[d->nodeStackSize - 1]->leftNode()); Q_ASSERT(left.end == d->currentIndex); ) const SetNodeData& right = *d->repository->m_dataRepository.itemFromIndex( d->nodeStack[d->nodeStackSize - 1]->rightNode()); d->startAtNode(&right); } } Q_ASSERT(d->nodeStackSize == 0 || d->currentIndex < d->nodeStack[0]->end()); if (d->repository->m_mutex) d->repository->m_mutex->unlock(); return *this; } BasicSetRepository::Index Set::Iterator::operator*() const { return d->currentIndex; } Set::Iterator Set::iterator() const { if (!m_tree || !m_repository) return Iterator(); QMutexLocker lock(m_repository->m_mutex); Iterator ret; ret.d->repository = m_repository; if (m_tree) ret.d->startAtNode(m_repository->m_dataRepository.itemFromIndex(m_tree)); return ret; } //Creates a set item with the given children., they must be valid, and they must be split around their split-position. uint SetRepositoryAlgorithms::createSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right) { if (!left) left = nodeFromIndex(leftNode); if (!right) right = nodeFromIndex(rightNode); Q_ASSERT(left->end() <= right->start()); SetNodeData set(left->start(), right->end(), leftNode, rightNode); Q_ASSERT(set.start() < set.end()); uint ret = repository.index(SetNodeDataRequest(&set, repository, setRepository)); Q_ASSERT(set.leftNode() >= 0x10000); Q_ASSERT(set.rightNode() >= 0x10000); Q_ASSERT(ret == repository.findIndex(SetNodeDataRequest(&set, repository, setRepository))); ifDebug(check(ret)); return ret; } //Constructs a set node from the given two sub-nodes. Those must be valid, they must not intersect, and they must have a correct split-hierarchy. //The do not need to be split around their computed split-position. uint SetRepositoryAlgorithms::computeSetFromNodes(uint leftNode, uint rightNode, const SetNodeData* left, const SetNodeData* right, uchar splitBit) { Q_ASSERT(left->end() <= right->start()); uint splitPosition = splitPositionForRange(left->start(), right->end(), splitBit); Q_ASSERT(splitPosition); if (splitPosition < left->end()) { //The split-position intersects the left node uint leftLeftNode = left->leftNode(); uint leftRightNode = left->rightNode(); const SetNodeData* leftLeft = this->getLeftNode(left); const SetNodeData* leftRight = this->getRightNode(left); Q_ASSERT(splitPosition >= leftLeft->end() && splitPosition <= leftRight->start()); //Create a new set from leftLeft, and from leftRight + right. That set will have the correct split-position. uint newRightNode = computeSetFromNodes(leftRightNode, rightNode, leftRight, right, splitBit); return createSetFromNodes(leftLeftNode, newRightNode, leftLeft); } else if (splitPosition > right->start()) { //The split-position intersects the right node uint rightLeftNode = right->leftNode(); uint rightRightNode = right->rightNode(); const SetNodeData* rightLeft = this->getLeftNode(right); const SetNodeData* rightRight = this->getRightNode(right); Q_ASSERT(splitPosition >= rightLeft->end() && splitPosition <= rightRight->start()); //Create a new set from left + rightLeft, and from rightRight. That set will have the correct split-position. uint newLeftNode = computeSetFromNodes(leftNode, rightLeftNode, left, rightLeft, splitBit); return createSetFromNodes(newLeftNode, rightRightNode, nullptr, rightRight); } else { return createSetFromNodes(leftNode, rightNode, left, right); } } uint SetRepositoryAlgorithms::set_union(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if (firstNode == secondNode) return firstNode; uint firstStart = first->start(), secondEnd = second->end(); if (firstStart >= secondEnd) return computeSetFromNodes(secondNode, firstNode, second, first, splitBit); uint firstEnd = first->end(), secondStart = second->start(); if (secondStart >= firstEnd) return computeSetFromNodes(firstNode, secondNode, first, second, splitBit); //The ranges of first and second do intersect uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if (splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the union on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); return createSetFromNodes(set_union(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit), set_union(firstRightNode, secondRightNode, firstRight, secondRight, splitBit)); } else if (splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to union that side of first with second. if (secondEnd <= splitPosition) { return createSetFromNodes(set_union(firstLeftNode, secondNode, firstLeft, second, splitBit), firstRightNode, nullptr, firstRight); } else { Q_ASSERT(secondStart >= splitPosition); return createSetFromNodes(firstLeftNode, set_union(firstRightNode, secondNode, firstRight, second, splitBit), firstLeft); } } else if (splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if (firstEnd <= splitPosition) { return createSetFromNodes(set_union(secondLeftNode, firstNode, secondLeft, first, splitBit), secondRightNode, nullptr, secondRight); } else { Q_ASSERT(firstStart >= splitPosition); return createSetFromNodes(secondLeftNode, set_union(secondRightNode, firstNode, secondRight, first, splitBit), secondLeft); } } else { //We would have stopped earlier of first and second don't intersect ifDebug(uint test = repository.findIndex(SetNodeDataRequest(first, repository, setRepository)); qCDebug( LANGUAGE) << "found index:" << test; ) Q_ASSERT(0); return 0; } } bool SetRepositoryAlgorithms::set_equals(const SetNodeData* lhs, const SetNodeData* rhs) { if (lhs->leftNode() != rhs->leftNode() || lhs->rightNode() != rhs->rightNode()) return false; else return true; } uint SetRepositoryAlgorithms::set_intersect(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if (firstNode == secondNode) return firstNode; if (first->start() >= second->end()) return 0; if (second->start() >= first->end()) return 0; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if (splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the intersection on both sides uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_intersect(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_intersect(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if (newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if (newLeftNode) return newLeftNode; else return newRightNode; } else if (splitPosition > firstStart && splitPosition < firstEnd) { uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we can completely ignore the other side of first. if (secondEnd <= splitPosition) { return set_intersect(firstLeftNode, secondNode, firstLeft, second, splitBit); } else { Q_ASSERT(secondStart >= splitPosition); return set_intersect(firstRightNode, secondNode, firstRight, second, splitBit); } } else if (splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if (firstEnd <= splitPosition) { return set_intersect(secondLeftNode, firstNode, secondLeft, first, splitBit); } else { Q_ASSERT(firstStart >= splitPosition); return set_intersect(secondRightNode, firstNode, secondRight, first, splitBit); } } else { //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } bool SetRepositoryAlgorithms::set_contains(const SetNodeData* node, Index index) { while (true) { if (node->start() > index || node->end() <= index) return false; if (node->contiguous()) return true; const SetNodeData* leftNode = nodeFromIndex(node->leftNode()); if (index < leftNode->end()) node = leftNode; else { const SetNodeData* rightNode = nodeFromIndex(node->rightNode()); node = rightNode; } } return false; } uint SetRepositoryAlgorithms::set_subtract(uint firstNode, uint secondNode, const SetNodeData* first, const SetNodeData* second, uchar splitBit) { if (firstNode == secondNode) return 0; if (first->start() >= second->end() || second->start() >= first->end()) return firstNode; //The ranges of first and second do intersect uint firstStart = first->start(), firstEnd = first->end(), secondStart = second->start(), secondEnd = second->end(); uint newStart = firstStart < secondStart ? firstStart : secondStart; uint newEnd = firstEnd > secondEnd ? firstEnd : secondEnd; //Compute the split-position for the resulting merged node uint splitPosition = splitPositionForRange(newStart, newEnd, splitBit); //Since the ranges overlap, we can be sure that either first or second contain splitPosition. //The node that contains it, will also be split by it. if (splitPosition > firstStart && splitPosition < firstEnd && splitPosition > secondStart && splitPosition < secondEnd) { //The split-position intersect with both first and second. Continue the subtract on both sides of the split-position, and merge it. uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); uint newLeftNode = set_subtract(firstLeftNode, secondLeftNode, firstLeft, secondLeft, splitBit); uint newRightNode = set_subtract(firstRightNode, secondRightNode, firstRight, secondRight, splitBit); if (newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if (newLeftNode) return newLeftNode; else return newRightNode; } else if (splitPosition > firstStart && splitPosition < firstEnd) { // Q_ASSERT(splitPosition >= firstLeft->end() && splitPosition <= firstRight->start()); uint firstLeftNode = first->leftNode(); uint firstRightNode = first->rightNode(); const SetNodeData* firstLeft = repository.itemFromIndex(firstLeftNode); const SetNodeData* firstRight = repository.itemFromIndex(firstRightNode); //splitPosition does not intersect second. That means that second is completely on one side of it. //So we only need to subtract that side of first with second. uint newLeftNode = firstLeftNode, newRightNode = firstRightNode; if (secondEnd <= splitPosition) { newLeftNode = set_subtract(firstLeftNode, secondNode, firstLeft, second, splitBit); } else { Q_ASSERT(secondStart >= splitPosition); newRightNode = set_subtract(firstRightNode, secondNode, firstRight, second, splitBit); } if (newLeftNode && newRightNode) return createSetFromNodes(newLeftNode, newRightNode); else if (newLeftNode) return newLeftNode; else return newRightNode; } else if (splitPosition > secondStart && splitPosition < secondEnd) { uint secondLeftNode = second->leftNode(); uint secondRightNode = second->rightNode(); const SetNodeData* secondLeft = repository.itemFromIndex(secondLeftNode); const SetNodeData* secondRight = repository.itemFromIndex(secondRightNode); Q_ASSERT(splitPosition >= secondLeft->end() && splitPosition <= secondRight->start()); if (firstEnd <= splitPosition) { return set_subtract(firstNode, secondLeftNode, first, secondLeft, splitBit); } else { Q_ASSERT(firstStart >= splitPosition); return set_subtract(firstNode, secondRightNode, first, secondRight, splitBit); } } else { //We would have stopped earlier of first and second don't intersect Q_ASSERT(0); return 0; } Q_ASSERT(0); } Set BasicSetRepository::createSetFromIndices(const std::vector& indices) { QMutexLocker lock(m_mutex); if (indices.empty()) return Set(); SetRepositoryAlgorithms alg(m_dataRepository, this); return Set(alg.setForIndices(indices.begin(), indices.end()), this); } Set BasicSetRepository::createSet(Index i) { QMutexLocker lock(m_mutex); SetNodeData data(i, i + 1); return Set(m_dataRepository.index(SetNodeDataRequest(&data, m_dataRepository, this)), this); } Set BasicSetRepository::createSet(const std::set& indices) { if (indices.empty()) return Set(); QMutexLocker lock(m_mutex); std::vector indicesVector; indicesVector.reserve(indices.size()); for (auto it = indices.begin(); it != indices.end(); ++it) indicesVector.push_back(*it); return createSetFromIndices(indicesVector); } BasicSetRepository::BasicSetRepository(const QString& name, KDevelop::ItemRepositoryRegistry* registry, bool delayedDeletion) : m_dataRepository(this, name, registry) , m_mutex(nullptr) , m_delayedDeletion(delayedDeletion) { m_mutex = m_dataRepository.mutex(); } struct StatisticsVisitor { explicit StatisticsVisitor(const SetDataRepository& _rep) : nodeCount(0) , badSplitNodeCount(0) , zeroRefCountNodes(0) , rep(_rep) { } bool operator()(const SetNodeData* item) { if (item->m_refCount == 0) ++zeroRefCountNodes; ++nodeCount; uint split = splitPositionForRange(item->start(), item->end()); if (item->hasSlaves()) if (split < rep.itemFromIndex(item->leftNode())->end() || split > rep.itemFromIndex(item->rightNode())->start()) ++badSplitNodeCount; return true; } uint nodeCount; uint badSplitNodeCount; uint zeroRefCountNodes; const SetDataRepository& rep; }; void BasicSetRepository::printStatistics() const { StatisticsVisitor stats(m_dataRepository); m_dataRepository.visitAllItems(stats); qCDebug(LANGUAGE) << "count of nodes:" << stats.nodeCount << "count of nodes with bad split:" << stats.badSplitNodeCount << "count of nodes with zero reference-count:" << stats.zeroRefCountNodes; } BasicSetRepository::~BasicSetRepository() = default; void BasicSetRepository::itemRemovedFromSets(uint /*index*/) { } void BasicSetRepository::itemAddedToSets(uint /*index*/) { } ////////////Set convenience functions////////////////// bool Set::contains(Index index) const { if (!m_tree || !m_repository) return false; QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); return alg.set_contains(m_repository->m_dataRepository.itemFromIndex(m_tree), index); } Set Set::operator +(const Set& first) const { if (!first.m_tree) return *this; else if (!m_tree || !m_repository) return first; Q_ASSERT(m_repository == first.m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); uint retNode = alg.set_union(m_tree, first.m_tree, m_repository->m_dataRepository.itemFromIndex( m_tree), m_repository->m_dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(retNode)); return Set(retNode, m_repository); } Set& Set::operator +=(const Set& first) { if (!first.m_tree) return *this; else if (!m_tree || !m_repository) { m_tree = first.m_tree; m_repository = first.m_repository; return *this; } QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); m_tree = alg.set_union(m_tree, first.m_tree, m_repository->m_dataRepository.itemFromIndex( m_tree), m_repository->m_dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator &(const Set& first) const { if (!first.m_tree || !m_tree) return Set(); Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); Set ret(alg.set_intersect(m_tree, first.m_tree, m_repository->m_dataRepository.itemFromIndex( m_tree), m_repository->m_dataRepository.itemFromIndex(first.m_tree)), m_repository); ifDebug(alg.check(ret.m_tree)); return ret; } Set& Set::operator &=(const Set& first) { if (!first.m_tree || !m_tree) { m_tree = 0; return *this; } Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); m_tree = alg.set_intersect(m_tree, first.m_tree, m_repository->m_dataRepository.itemFromIndex( m_tree), m_repository->m_dataRepository.itemFromIndex(first.m_tree)); ifDebug(alg.check(m_tree)); return *this; } Set Set::operator -(const Set& rhs) const { if (!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); Set ret(alg.set_subtract(m_tree, rhs.m_tree, m_repository->m_dataRepository.itemFromIndex( m_tree), m_repository->m_dataRepository.itemFromIndex(rhs.m_tree)), m_repository); ifDebug(alg.check(ret.m_tree)); return ret; } Set& Set::operator -=(const Set& rhs) { if (!m_tree || !rhs.m_tree) return *this; Q_ASSERT(m_repository); QMutexLocker lock(m_repository->m_mutex); SetRepositoryAlgorithms alg(m_repository->m_dataRepository, m_repository); m_tree = alg.set_subtract(m_tree, rhs.m_tree, m_repository->m_dataRepository.itemFromIndex( m_tree), m_repository->m_dataRepository.itemFromIndex(rhs.m_tree)); ifDebug(alg.check(m_tree)); return *this; } BasicSetRepository* Set::repository() const { return m_repository; } void Set::staticRef() { if (!m_tree) return; QMutexLocker lock(m_repository->m_mutex); SetNodeData* data = m_repository->m_dataRepository.dynamicItemFromIndexSimple(m_tree); ++data->m_refCount; } ///Mutex must be locked void Set::unrefNode(uint current) { SetNodeData* data = m_repository->m_dataRepository.dynamicItemFromIndexSimple(current); Q_ASSERT(data->m_refCount); --data->m_refCount; if (!m_repository->delayedDeletion()) { if (data->m_refCount == 0) { if (data->leftNode()) { Q_ASSERT(data->rightNode()); unrefNode(data->rightNode()); unrefNode(data->leftNode()); } else { //Deleting a leaf Q_ASSERT(data->end() - data->start() == 1); m_repository->itemRemovedFromSets(data->start()); } m_repository->m_dataRepository.deleteItem(current); } } } ///Decrease the static reference-count of this set by one. This set must have a reference-count > 1. ///If this set reaches the reference-count zero, it will be deleted, and all sub-nodes that also reach the reference-count zero ///will be deleted as well. @warning Either protect ALL your sets by using reference-counting, or don't use it at all. void Set::staticUnref() { if (!m_tree) return; QMutexLocker lock(m_repository->m_mutex); unrefNode(m_tree); } StringSetRepository::StringSetRepository(const QString& name) : Utils::BasicSetRepository(name) { } void StringSetRepository::itemRemovedFromSets(uint index) { ///Call the IndexedString destructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); KDevelop::enableDUChainReferenceCounting(&string, sizeof(KDevelop::IndexedString)); string.~IndexedString(); //Call destructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(&string); } void StringSetRepository::itemAddedToSets(uint index) { ///Call the IndexedString constructor with enabled reference-counting KDevelop::IndexedString string = KDevelop::IndexedString::fromIndex(index); char data[sizeof(KDevelop::IndexedString)]; KDevelop::enableDUChainReferenceCounting(data, sizeof(KDevelop::IndexedString)); new (data) KDevelop::IndexedString(string); //Call constructor with enabled reference-counting KDevelop::disableDUChainReferenceCounting(data); } } diff --git a/kdevplatform/language/util/setrepository.h b/kdevplatform/language/util/setrepository.h index 640980d7df..4d8a5bebcd 100644 --- a/kdevplatform/language/util/setrepository.h +++ b/kdevplatform/language/util/setrepository.h @@ -1,548 +1,548 @@ /*************************************************************************** Copyright 2007 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_SETREPOSITORY_H #define KDEVPLATFORM_SETREPOSITORY_H #include "basicsetrepository.h" #include #include /** * This header defines convenience-class that allow handling set-repositories using the represented higher-level objects instead * of indices to them. * */ namespace Utils { /** * Use this class to conveniently iterate over the items in a set. * @tparam T The type the indices will be converted to * @tparam Conversion Should be a class that has a toIndex member function that takes an object of type T as parameter, and returns an index, * and a toItem member function that takes an index, and returns an item of type T. * */ template class ConvenientIterator : public Conversion { public: explicit ConvenientIterator(const Set::Iterator& it = Set::Iterator()) : m_it(it) { } explicit ConvenientIterator(const Set& set) : m_it(set.iterator()) { } operator bool() const { return m_it; } ConvenientIterator& operator++() { ++m_it; return *this; } T operator*() const { return Conversion::toItem(*m_it); } uint index() const { return *m_it; } private: Set::Iterator m_it; }; struct DummyLocker { }; template struct IdentityConversion { static T toIndex(const T& t) { return t; } static T toItem(const T& t) { return t; } }; ///This is a virtual set-node that allows conveniently iterating through the tree-structure, ///accessing the contained items directly, and accessing the ranges. template class VirtualSetNode { private: using ClassType = VirtualSetNode; public: inline explicit VirtualSetNode(const SetNodeData* data = nullptr) : m_data(data) { } inline bool isValid() const { return ( bool )m_data; } ///If this returns false, a left and a right node are available. ///If this returns true, this node represents a single item, that can be retrieved by calling item() or operator*. inline bool isFinalNode() const { return m_data->leftNode() == 0; } inline T firstItem() const { return Conversion::toItem(start()); } inline T lastItem() const { return Conversion::toItem(end() - 1); } inline T operator*() const { return Conversion::toItem(start()); } inline ClassType leftChild() const { if (m_data->leftNode()) return ClassType(StaticRepository::repository()->nodeFromIndex(m_data->leftNode())); else return ClassType(nullptr); } inline ClassType rightChild() const { if (m_data->rightNode()) return ClassType(StaticRepository::repository()->nodeFromIndex(m_data->rightNode())); else return ClassType(nullptr); } ///Returns the start of this node's range. If this is a final node, the length of the range is 1. inline uint start() const { return m_data->start(); } ///Returns the end of this node's range. inline uint end() const { return m_data->end(); } private: const SetNodeData* m_data; }; template class StorableSet : public Conversion { public: - typedef VirtualSetNode Node; + using Node = VirtualSetNode; StorableSet(const StorableSet& rhs) : m_setIndex(rhs.m_setIndex) { StaticAccessLocker lock; Q_UNUSED(lock); if (doReferenceCounting) set().staticRef(); } explicit StorableSet(const std::set& indices) { StaticAccessLocker lock; Q_UNUSED(lock); m_setIndex = StaticRepository::repository()->createSet(indices).setIndex(); if (doReferenceCounting) set().staticRef(); } StorableSet() { } ~StorableSet() { StaticAccessLocker lock; Q_UNUSED(lock); if (doReferenceCounting) set().staticUnref(); } void insert(const T& t) { insertIndex(Conversion::toIndex(t)); } bool isEmpty() const { return m_setIndex == 0; } uint count() const { return set().count(); } void insertIndex(uint index) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set addedSet = StaticRepository::repository()->createSet(index); if (doReferenceCounting) addedSet.staticRef(); set += addedSet; m_setIndex = set.setIndex(); if (doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); addedSet.staticUnref(); } } void remove(const T& t) { removeIndex(Conversion::toIndex(t)); } void removeIndex(uint index) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set removedSet = StaticRepository::repository()->createSet(index); if (doReferenceCounting) { removedSet.staticRef(); } set -= removedSet; m_setIndex = set.setIndex(); if (doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); removedSet.staticUnref(); } } Set set() const { return Set(m_setIndex, StaticRepository::repository()); } bool contains(const T& item) const { return containsIndex(Conversion::toIndex(item)); } bool containsIndex(uint index) const { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); return set.contains(index); } StorableSet& operator +=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set otherSet(rhs.m_setIndex, StaticRepository::repository()); set += otherSet; m_setIndex = set.setIndex(); if (doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); } return *this; } StorableSet& operator -=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set otherSet(rhs.m_setIndex, StaticRepository::repository()); set -= otherSet; m_setIndex = set.setIndex(); if (doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); } return *this; } StorableSet& operator &=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); Set set(m_setIndex, StaticRepository::repository()); Set oldSet(set); Set otherSet(rhs.m_setIndex, StaticRepository::repository()); set &= otherSet; m_setIndex = set.setIndex(); if (doReferenceCounting) { set.staticRef(); oldSet.staticUnref(); } return *this; } StorableSet& operator=(const StorableSet& rhs) { StaticAccessLocker lock; Q_UNUSED(lock); if (doReferenceCounting) set().staticUnref(); m_setIndex = rhs.m_setIndex; if (doReferenceCounting) set().staticRef(); return *this; } StorableSet operator +(const StorableSet& rhs) const { StorableSet ret(*this); ret += rhs; return ret; } StorableSet operator -(const StorableSet& rhs) const { StorableSet ret(*this); ret -= rhs; return ret; } StorableSet operator &(const StorableSet& rhs) const { StorableSet ret(*this); ret &= rhs; return ret; } bool operator==(const StorableSet& rhs) const { return m_setIndex == rhs.m_setIndex; } - typedef ConvenientIterator Iterator; + using Iterator = ConvenientIterator; Iterator iterator() const { return ConvenientIterator(set()); } Node node() const { return Node(StaticRepository::repository()->nodeFromIndex(m_setIndex)); } uint setIndex() const { return m_setIndex; } private: uint m_setIndex = 0; }; template uint qHash(const StorableSet& set) { return set.setIndex(); } /** This is a helper-class that helps inserting a bunch of items into a set without caring about grouping them together. * * It creates a much better tree-structure if many items are inserted at one time, and this class helps doing that in * cases where there is no better choice then storing a temporary list of items and inserting them all at once. * * This set will then care about really inserting them into the repository once the real set is requested. * * @todo eventually make this unnecessary * * @tparam T Should be the type that should be dealt * @tparam Conversion Should be a class that has a toIndex member function that takes an object of type T as parameter, and returns an index, * and a toItem member function that takes an index, and returns an item of type T. **/ template class LazySet : public Conversion { public: /** @param rep The repository the set should belong/belongs to * @param lockBeforeAccess If this is nonzero, the given mutex will be locked before each modification to the repository. * @param basicSet If this is explicitly given, the given set will be used as base. However it will not be changed. * * @warning Watch for deadlocks, never use this class while the mutex given through lockBeforeAccess is locked */ explicit LazySet(BasicSetRepository* rep, QMutex* lockBeforeAccess = nullptr, const Set& basicSet = Set()) : m_rep( rep) , m_set(basicSet) , m_lockBeforeAccess(lockBeforeAccess) { } void insert(const T& t) { if (!m_temporaryRemoveIndices.empty()) apply(); m_temporaryIndices.insert(Conversion::toIndex(t)); } void insertIndex(uint index) { if (!m_temporaryRemoveIndices.empty()) apply(); m_temporaryIndices.insert(index); } void remove(const T& t) { if (!m_temporaryIndices.empty()) apply(); m_temporaryRemoveIndices.insert(Conversion::toIndex(t)); } ///Returns the set this LazySet represents. When this is called, the set is constructed in the repository. Set set() const { apply(); return m_set; } ///@warning this is expensive, because the set is constructed bool contains(const T& item) const { QMutexLocker l(m_lockBeforeAccess); uint index = Conversion::toIndex(item); if (m_temporaryRemoveIndices.empty()) { //Simplification without creating the set if (m_temporaryIndices.find(index) != m_temporaryIndices.end()) return true; return m_set.contains(index); } return set().contains(index); } LazySet& operator +=(const Set& set) { if (!m_temporaryRemoveIndices.empty()) apply(); QMutexLocker l(m_lockBeforeAccess); m_set += set; return *this; } LazySet& operator -=(const Set& set) { if (!m_temporaryIndices.empty()) apply(); QMutexLocker l(m_lockBeforeAccess); m_set -= set; return *this; } LazySet operator +(const Set& set) const { apply(); QMutexLocker l(m_lockBeforeAccess); Set ret = m_set + set; return LazySet(m_rep, m_lockBeforeAccess, ret); } LazySet operator -(const Set& set) const { apply(); QMutexLocker l(m_lockBeforeAccess); Set ret = m_set - set; return LazySet(m_rep, m_lockBeforeAccess, ret); } void clear() { QMutexLocker l(m_lockBeforeAccess); m_set = Set(); m_temporaryIndices.clear(); m_temporaryRemoveIndices.clear(); } ConvenientIterator iterator() const { apply(); return ConvenientIterator(set()); } private: void apply() const { if (!m_temporaryIndices.empty()) { QMutexLocker l(m_lockBeforeAccess); Set tempSet = m_rep->createSet(m_temporaryIndices); m_temporaryIndices.clear(); m_set += tempSet; } if (!m_temporaryRemoveIndices.empty()) { QMutexLocker l(m_lockBeforeAccess); Set tempSet = m_rep->createSet(m_temporaryRemoveIndices); m_temporaryRemoveIndices.clear(); m_set -= tempSet; } } BasicSetRepository* m_rep; mutable Set m_set; QMutex* m_lockBeforeAccess; - typedef std::set IndexList; + using IndexList = std::set; mutable IndexList m_temporaryIndices; mutable IndexList m_temporaryRemoveIndices; }; ///Persistent repository that manages string-sets, also correctly increasing the string reference-counts as needed struct KDEVPLATFORMLANGUAGE_EXPORT StringSetRepository : public Utils::BasicSetRepository { explicit StringSetRepository(const QString& name); void itemRemovedFromSets(uint index) override; void itemAddedToSets(uint index) override; }; } #endif diff --git a/kdevplatform/qtcompat_p.h b/kdevplatform/qtcompat_p.h index c71bbcefdd..0b01a4a16f 100644 --- a/kdevplatform/qtcompat_p.h +++ b/kdevplatform/qtcompat_p.h @@ -1,82 +1,82 @@ /* Copyright (c) 2017 Kevin Funk #include #if QT_VERSION < QT_VERSION_CHECK(5,7,0) namespace QtPrivate { template struct QAddConst { - typedef const T Type; + using Type = const T; }; } // this adds const to non-const objects (like std::as_const) template Q_DECL_CONSTEXPR typename QtPrivate::QAddConst::Type &qAsConst(T &t) Q_DECL_NOTHROW { return t; } // prevent rvalue arguments: template void qAsConst(const T &&) Q_DECL_EQ_DELETE; #endif // compat for Q_FALLTHROUGH #if QT_VERSION < QT_VERSION_CHECK(5,8,0) #if defined(__has_cpp_attribute) # if __has_cpp_attribute(fallthrough) # define Q_FALLTHROUGH() [[fallthrough]] # elif __has_cpp_attribute(clang::fallthrough) # define Q_FALLTHROUGH() [[clang::fallthrough]] # elif __has_cpp_attribute(gnu::fallthrough) # define Q_FALLTHROUGH() [[gnu::fallthrough]] # endif #endif #ifndef Q_FALLTHROUGH # if defined(__GNUC__) && !defined(__INTEL_COMPILER) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 700) # define Q_FALLTHROUGH() __attribute__((fallthrough)) # else # define Q_FALLTHROUGH() (void)0 # endif #endif #endif namespace QtCompat { // TODO: Just use QDir::listSeparator once we depend on Qt 5.6 Q_DECL_CONSTEXPR inline QChar listSeparator() Q_DECL_NOTHROW { #if QT_VERSION < QT_VERSION_CHECK(5,6,0) #ifdef Q_OS_WIN return QLatin1Char(';'); #else return QLatin1Char(':'); #endif #else return QDir::listSeparator(); #endif } } #endif diff --git a/kdevplatform/serialization/indexedstring.cpp b/kdevplatform/serialization/indexedstring.cpp index bb8dc827ea..8bb025163b 100644 --- a/kdevplatform/serialization/indexedstring.cpp +++ b/kdevplatform/serialization/indexedstring.cpp @@ -1,406 +1,406 @@ /* This file is part of KDevelop Copyright 2008 David Nolden Copyright 2016 Milian Wolff 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 General Public License along with this program. If not, see . */ #include "indexedstring.h" #include "serialization/stringrepository.h" #include "referencecounting.h" using namespace KDevelop; namespace { struct IndexedStringData { unsigned short length; uint refCount; uint itemSize() const { return sizeof(IndexedStringData) + length; } uint hash() const { IndexedString::RunningHash running; const char* str = (( const char* )this) + sizeof(IndexedStringData); for (int a = length - 1; a >= 0; --a) { running.append(*str); ++str; } return running.hash; } }; inline void increase(uint& val) { ++val; } inline void decrease(uint& val) { --val; } struct IndexedStringRepositoryItemRequest { //The text is supposed to be utf8 encoded IndexedStringRepositoryItemRequest(const char* text, uint hash, unsigned short length) : m_hash(hash) , m_length(length) , m_text(text) { } enum { AverageSize = 10 //This should be the approximate average size of an Item }; - typedef uint HashType; + using HashType = uint; //Should return the hash-value associated with this request(For example the hash of a string) HashType hash() const { return m_hash; } //Should return the size of an item created with createItem uint itemSize() const { return sizeof(IndexedStringData) + m_length; } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(IndexedStringData* item) const { item->length = m_length; item->refCount = 0; ++item; memcpy(item, m_text, m_length); } static void destroy(IndexedStringData* item, AbstractItemRepository&) { Q_UNUSED(item); //Nothing to do here (The object is not intelligent) } static bool persistent(const IndexedStringData* item) { return ( bool )item->refCount; } //Should return whether the here requested item equals the given item bool equals(const IndexedStringData* item) const { return item->length == m_length && (memcmp(++item, m_text, m_length) == 0); } uint m_hash; unsigned short m_length; const char* m_text; }; inline const char* c_strFromItem(const IndexedStringData* item) { return reinterpret_cast(item + 1); } ///@param item must be valid(nonzero) inline QString stringFromItem(const IndexedStringData* item) { return QString::fromUtf8(c_strFromItem(item), item->length); } inline QByteArray arrayFromItem(const IndexedStringData* item) { return QByteArray(c_strFromItem(item), item->length); } inline bool isSingleCharIndex(uint index) { return (index & 0xffff0000) == 0xffff0000; } inline uint charToIndex(char c) { return 0xffff0000 | c; } inline char indexToChar(uint index) { Q_ASSERT(isSingleCharIndex(index)); return static_cast(index & 0xff); } using IndexedStringRepository = ItemRepository; using IndexedStringRepositoryManagerBase = RepositoryManager; class IndexedStringRepositoryManager : public IndexedStringRepositoryManagerBase { public: IndexedStringRepositoryManager() : IndexedStringRepositoryManagerBase(QStringLiteral("String Index")) { repository()->setMutex(&m_mutex); } private: // non-recursive mutex to increase speed QMutex m_mutex; }; IndexedStringRepository* globalIndexedStringRepository() { static IndexedStringRepositoryManager manager; return manager.repository(); } template auto readRepo(ReadAction action)->decltype(action(globalIndexedStringRepository())) { const auto* repo = globalIndexedStringRepository(); QMutexLocker lock(repo->mutex()); return action(repo); } template auto editRepo(EditAction action)->decltype(action(globalIndexedStringRepository())) { auto* repo = globalIndexedStringRepository(); QMutexLocker lock(repo->mutex()); return action(repo); } inline void ref(IndexedString* string) { const uint index = string->index(); if (index && !isSingleCharIndex(index)) { if (shouldDoDUChainReferenceCounting(string)) { editRepo([index](IndexedStringRepository* repo) { increase(repo->dynamicItemFromIndexSimple(index)->refCount); }); } } } inline void deref(IndexedString* string) { const uint index = string->index(); if (index && !isSingleCharIndex(index)) { if (shouldDoDUChainReferenceCounting(string)) { editRepo([index](IndexedStringRepository* repo) { decrease(repo->dynamicItemFromIndexSimple(index)->refCount); }); } } } } IndexedString::IndexedString() { } ///@param str must be a utf8 encoded string, does not need to be 0-terminated. ///@param length must be its length in bytes. IndexedString::IndexedString(const char* str, unsigned short length, uint hash) { if (!length) { m_index = 0; } else if (length == 1) { m_index = charToIndex(str[0]); } else { const auto request = IndexedStringRepositoryItemRequest(str, hash ? hash : hashString(str, length), length); bool refcount = shouldDoDUChainReferenceCounting(this); m_index = editRepo([request, refcount](IndexedStringRepository* repo) { auto index = repo->index(request); if (refcount) { increase(repo->dynamicItemFromIndexSimple(index)->refCount); } return index; }); } } IndexedString::IndexedString(char c) : m_index(charToIndex(c)) {} IndexedString::IndexedString(const QUrl& url) : IndexedString(url.isLocalFile() ? url.toLocalFile() : url.toString()) { Q_ASSERT(url.isEmpty() || !url.isRelative()); #if !defined(QT_NO_DEBUG) if (url != url.adjusted(QUrl::NormalizePathSegments)) { qWarning() << "wrong url" << url << url.adjusted(QUrl::NormalizePathSegments); } #endif Q_ASSERT(url == url.adjusted(QUrl::NormalizePathSegments)); } IndexedString::IndexedString(const QString& string) : IndexedString(string.toUtf8()) {} IndexedString::IndexedString(const char* str) : IndexedString(str, str ? qstrlen(str) : 0) {} IndexedString::IndexedString(const QByteArray& str) : IndexedString(str.constData(), str.length()) {} IndexedString::~IndexedString() { deref(this); } IndexedString::IndexedString(const IndexedString& rhs) : m_index(rhs.m_index) { ref(this); } IndexedString& IndexedString::operator=(const IndexedString& rhs) { if (m_index == rhs.m_index) { return *this; } deref(this); m_index = rhs.m_index; ref(this); return *this; } QUrl IndexedString::toUrl() const { if (isEmpty()) { return {}; } QUrl ret = QUrl::fromUserInput(str()); Q_ASSERT(!ret.isRelative()); return ret; } QString IndexedString::str() const { if (!m_index) { return QString(); } else if (isSingleCharIndex(m_index)) { return QString(QLatin1Char(indexToChar(m_index))); } else { const uint index = m_index; return readRepo([index](const IndexedStringRepository* repo) { return stringFromItem(repo->itemFromIndex(index)); }); } } int IndexedString::length() const { return lengthFromIndex(m_index); } int IndexedString::lengthFromIndex(uint index) { if (!index) { return 0; } else if (isSingleCharIndex(index)) { return 1; } else { return readRepo([index](const IndexedStringRepository* repo) { return repo->itemFromIndex(index)->length; }); } } const char* IndexedString::c_str() const { if (!m_index) { return nullptr; } else if (isSingleCharIndex(m_index)) { #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN const uint offset = 0; #else const uint offset = 3; #endif return reinterpret_cast(&m_index) + offset; } else { const uint index = m_index; return readRepo([index](const IndexedStringRepository* repo) { return c_strFromItem(repo->itemFromIndex(index)); }); } } QByteArray IndexedString::byteArray() const { if (!m_index) { return QByteArray(); } else if (isSingleCharIndex(m_index)) { return QByteArray(1, indexToChar(m_index)); } else { const uint index = m_index; return readRepo([index](const IndexedStringRepository* repo) { return arrayFromItem(repo->itemFromIndex(index)); }); } } uint IndexedString::hashString(const char* str, unsigned short length) { RunningHash running; for (int a = length - 1; a >= 0; --a) { running.append(*str); ++str; } return running.hash; } uint IndexedString::indexForString(const char* str, short unsigned length, uint hash) { if (!length) { return 0; } else if (length == 1) { return charToIndex(str[0]); } else { const auto request = IndexedStringRepositoryItemRequest(str, hash ? hash : hashString(str, length), length); return editRepo([request](IndexedStringRepository* repo) { return repo->index(request); }); } } uint IndexedString::indexForString(const QString& str, uint hash) { const QByteArray array(str.toUtf8()); return indexForString(array.constBegin(), array.size(), hash); } QDebug operator<<(QDebug s, const IndexedString& string) { s.nospace() << string.str(); return s.space(); } diff --git a/kdevplatform/serialization/itemrepository.h b/kdevplatform/serialization/itemrepository.h index 25d5504e76..131a2d05b6 100644 --- a/kdevplatform/serialization/itemrepository.h +++ b/kdevplatform/serialization/itemrepository.h @@ -1,2370 +1,2370 @@ /* 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. */ #ifndef KDEVPLATFORM_ITEMREPOSITORY_H #define KDEVPLATFORM_ITEMREPOSITORY_H #include #include #include #include #include #include "referencecounting.h" #include "abstractitemrepository.h" #include "repositorymanager.h" #include "itemrepositoryregistry.h" //#define DEBUG_MONSTERBUCKETS // #define DEBUG_ITEMREPOSITORY_LOADING // #define ifDebugInfiniteRecursion(x) x #define ifDebugInfiniteRecursion(x) // #define ifDebugLostSpace(x) x #define ifDebugLostSpace(x) // #define DEBUG_INCORRECT_DELETE //Makes sure that all items stay reachable through the basic hash // #define DEBUG_ITEM_REACHABILITY ///@todo Dynamic bucket hash size #ifdef DEBUG_ITEM_REACHABILITY #define ENSURE_REACHABLE(bucket) Q_ASSERT(allItemsReachable(bucket)); #define IF_ENSURE_REACHABLE(x) x #else #define ENSURE_REACHABLE(bucket) #define IF_ENSURE_REACHABLE(x) #endif #define ITEMREPOSITORY_USE_MMAP_LOADING //Assertion macro that prevents warnings if debugging is disabled //Only use it to verify values, it should not call any functions, since else the function will even be called in release mode #ifdef QT_NO_DEBUG #define VERIFY(X) if (!(X)) {qWarning() << "Failed to verify expression" << # X;} #else #define VERIFY(X) Q_ASSERT(X) #endif ///When this is uncommented, a 64-bit test-value is written behind the area an item is allowed to write into before ///createItem(..) is called, and an assertion triggers when it was changed during createItem(), which means createItem wrote too long. ///The problem: This temporarily overwrites valid data in the following item, so it will cause serious problems if that data is accessed ///during the call to createItem(). // #define DEBUG_WRITING_EXTENTS class TestItemRepository; namespace KDevelop { /** * This file implements a generic bucket-based indexing repository, that can be used for example to index strings. * * All you need to do is define your item type that you want to store into the repository, and create a request item * similar to ExampleItemRequest that compares and fills the defined item type. * * For example the string repository uses "unsigned short" as item-type, uses that actual value to store the length of the string, * and uses the space behind to store the actual string content. * * @see AbstractItemRepository * @see ItemRepository * * @see ExampleItem * @see ExampleItemRequest * * @see typerepository.h * @see stringrepository.h * @see indexedstring.h */ enum { ItemRepositoryBucketSize = 1 << 16, ItemRepositoryBucketLimit = 1 << 16 }; /** * Buckets are the memory-units that are used to store the data in an ItemRepository. * * About monster buckets: Normally a bucket has a size of 64kb, but when an item is * allocated that is larger than that, a "monster bucket" is allocated, which spans the * space of multiple buckets. */ template class Bucket { public: enum { AdditionalSpacePerItem = 2 }; enum { ObjectMapSize = ((ItemRepositoryBucketSize / ItemRequest::AverageSize) * 3) / 2 + 1, MaxFreeItemsForHide = 0, //When less than this count of free items in one buckets is reached, the bucket is removed from the global list of buckets with free items MaxFreeSizeForHide = fixedItemSize ? fixedItemSize : 0, //Only when the largest free size is smaller then this, the bucket is taken from the free list MinFreeItemsForReuse = 10,//When this count of free items in one bucket is reached, consider re-assigning them to new requests MinFreeSizeForReuse = ItemRepositoryBucketSize / 20 //When the largest free item is bigger then this, the bucket is automatically added to the free list }; enum { NextBucketHashSize = ObjectMapSize, //Affects the average count of bucket-chains that need to be walked in ItemRepository::index. Must be a multiple of ObjectMapSize DataSize = sizeof(char) + sizeof(unsigned int) * 3 + ItemRepositoryBucketSize + sizeof(short unsigned int) * (ObjectMapSize + NextBucketHashSize + 1) }; enum { CheckStart = 0xff00ff1, CheckEnd = 0xfafcfb }; Bucket() { } ~Bucket() { if (m_data != m_mappedData) { delete[] m_data; delete[] m_nextBucketHash; delete[] m_objectMap; } } void initialize(int monsterBucketExtent) { if (!m_data) { m_monsterBucketExtent = monsterBucketExtent; m_available = ItemRepositoryBucketSize; m_data = new char[ItemRepositoryBucketSize + monsterBucketExtent * DataSize]; #ifndef QT_NO_DEBUG memset(m_data, 0, (ItemRepositoryBucketSize + monsterBucketExtent * DataSize) * sizeof(char)); #endif //The bigger we make the map, the lower the probability of a clash(and thus bad performance). However it increases memory usage. m_objectMap = new short unsigned int[ObjectMapSize]; memset(m_objectMap, 0, ObjectMapSize * sizeof(short unsigned int)); m_nextBucketHash = new short unsigned int[NextBucketHashSize]; memset(m_nextBucketHash, 0, NextBucketHashSize * sizeof(short unsigned int)); m_changed = true; m_dirty = false; m_lastUsed = 0; } } template void readValue(char*& from, T& to) { to = *reinterpret_cast(from); from += sizeof(T); } void initializeFromMap(char* current) { if (!m_data) { char* start = current; readValue(current, m_monsterBucketExtent); Q_ASSERT(current - start == 4); readValue(current, m_available); m_objectMap = reinterpret_cast(current); current += sizeof(short unsigned int) * ObjectMapSize; m_nextBucketHash = reinterpret_cast(current); current += sizeof(short unsigned int) * NextBucketHashSize; readValue(current, m_largestFreeItem); readValue(current, m_freeItemCount); readValue(current, m_dirty); m_data = current; m_mappedData = current; m_changed = false; m_lastUsed = 0; VERIFY(current - start == (DataSize - ItemRepositoryBucketSize)); } } void store(QFile* file, size_t offset) { if (!m_data) return; if (static_cast(file->size()) < offset + (1 + m_monsterBucketExtent) * DataSize) file->resize(offset + (1 + m_monsterBucketExtent) * DataSize); file->seek(offset); file->write(( char* )&m_monsterBucketExtent, sizeof(unsigned int)); file->write(( char* )&m_available, sizeof(unsigned int)); file->write(( char* )m_objectMap, sizeof(short unsigned int) * ObjectMapSize); file->write(( char* )m_nextBucketHash, sizeof(short unsigned int) * NextBucketHashSize); file->write(( char* )&m_largestFreeItem, sizeof(short unsigned int)); file->write(( char* )&m_freeItemCount, sizeof(unsigned int)); file->write(( char* )&m_dirty, sizeof(bool)); file->write(m_data, ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize); if (static_cast(file->pos()) != offset + (1 + m_monsterBucketExtent) * DataSize) { KMessageBox::error(nullptr, i18n("Failed writing to %1, probably the disk is full", file->fileName())); abort(); } m_changed = false; #ifdef DEBUG_ITEMREPOSITORY_LOADING { file->flush(); file->seek(offset); uint available, freeItemCount, monsterBucketExtent; short unsigned int largestFree; bool dirty; short unsigned int* m = new short unsigned int[ObjectMapSize]; short unsigned int* h = new short unsigned int[NextBucketHashSize]; file->read(( char* )&monsterBucketExtent, sizeof(unsigned int)); char* d = new char[ItemRepositoryBucketSize + monsterBucketExtent * DataSize]; file->read(( char* )&available, sizeof(unsigned int)); file->read(( char* )m, sizeof(short unsigned int) * ObjectMapSize); file->read(( char* )h, sizeof(short unsigned int) * NextBucketHashSize); file->read(( char* )&largestFree, sizeof(short unsigned int)); file->read(( char* )&freeItemCount, sizeof(unsigned int)); file->read(( char* )&dirty, sizeof(bool)); file->read(d, ItemRepositoryBucketSize); Q_ASSERT(monsterBucketExtent == m_monsterBucketExtent); Q_ASSERT(available == m_available); Q_ASSERT(memcmp(d, m_data, ItemRepositoryBucketSize + monsterBucketExtent * DataSize) == 0); Q_ASSERT(memcmp(m, m_objectMap, sizeof(short unsigned int) * ObjectMapSize) == 0); Q_ASSERT(memcmp(h, m_nextBucketHash, sizeof(short unsigned int) * NextBucketHashSize) == 0); Q_ASSERT(m_largestFreeItem == largestFree); Q_ASSERT(m_freeItemCount == freeItemCount); Q_ASSERT(m_dirty == dirty); Q_ASSERT(static_cast(file->pos()) == offset + DataSize + m_monsterBucketExtent * DataSize); delete[] d; delete[] m; delete[] h; } #endif } inline char* data() { return m_data; } inline uint dataSize() const { return ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize; } //Tries to find the index this item has in this bucket, or returns zero if the item isn't there yet. unsigned short findIndex(const ItemRequest& request) const { m_lastUsed = 0; unsigned short localHash = request.hash() % ObjectMapSize; unsigned short index = m_objectMap[localHash]; unsigned short follower = 0; //Walk the chain of items with the same local hash while (index && (follower = followerIndex(index)) && !(request.equals(itemFromIndex(index)))) index = follower; if (index && request.equals(itemFromIndex(index))) { return index; //We have found the item } return 0; } //Tries to get the index within this bucket, or returns zero. Will put the item into the bucket if there is room. //Created indices will never begin with 0xffff____, so you can use that index-range for own purposes. unsigned short index(const ItemRequest& request, unsigned int itemSize) { m_lastUsed = 0; unsigned short localHash = request.hash() % ObjectMapSize; unsigned short index = m_objectMap[localHash]; unsigned short insertedAt = 0; unsigned short follower = 0; //Walk the chain of items with the same local hash while (index && (follower = followerIndex(index)) && !(request.equals(itemFromIndex(index)))) index = follower; if (index && request.equals(itemFromIndex(index))) return index; //We have found the item ifDebugLostSpace(Q_ASSERT(!lostSpace()); ) prepareChange(); unsigned int totalSize = itemSize + AdditionalSpacePerItem; if (m_monsterBucketExtent) { ///This is a monster-bucket. Other rules are applied here. Only one item can be allocated, and that must be bigger than the bucket data Q_ASSERT(totalSize > ItemRepositoryBucketSize); Q_ASSERT(m_available); m_available = 0; insertedAt = AdditionalSpacePerItem; setFollowerIndex(insertedAt, 0); Q_ASSERT(m_objectMap[localHash] == 0); m_objectMap[localHash] = insertedAt; if (markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); request.createItem(reinterpret_cast(m_data + insertedAt)); if (markForReferenceCounting) disableDUChainReferenceCounting(m_data); return insertedAt; } //The second condition is needed, else we can get problems with zero-length items and an overflow in insertedAt to zero if (totalSize > m_available || (!itemSize && totalSize == m_available)) { //Try finding the smallest freed item that can hold the data unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; unsigned short freeChunkSize = 0; ///@todo Achieve this without full iteration while (currentIndex && freeSize(currentIndex) > itemSize) { unsigned short follower = followerIndex(currentIndex); if (follower && freeSize(follower) >= itemSize) { //The item also fits into the smaller follower, so use that one previousIndex = currentIndex; currentIndex = follower; } else { //The item fits into currentIndex, but not into the follower. So use currentIndex freeChunkSize = freeSize(currentIndex) - itemSize; //We need 2 bytes to store the free size if (freeChunkSize != 0 && freeChunkSize < AdditionalSpacePerItem + 2) { //we can not manage the resulting free chunk as a separate item, so we cannot use this position. //Just pick the biggest free item, because there we can be sure that //either we can manage the split, or we cannot do anything at all in this bucket. freeChunkSize = freeSize(m_largestFreeItem) - itemSize; if (freeChunkSize == 0 || freeChunkSize >= AdditionalSpacePerItem + 2) { previousIndex = 0; currentIndex = m_largestFreeItem; } else { currentIndex = 0; } } break; } } if (!currentIndex || freeSize(currentIndex) < (totalSize - AdditionalSpacePerItem)) return 0; if (previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //Took one free item out of the chain ifDebugLostSpace(Q_ASSERT(( uint )lostSpace() == ( uint )(freeSize(currentIndex) + AdditionalSpacePerItem)); ) if (freeChunkSize) { Q_ASSERT(freeChunkSize >= AdditionalSpacePerItem + 2); unsigned short freeItemSize = freeChunkSize - AdditionalSpacePerItem; unsigned short freeItemPosition; //Insert the resulting free chunk into the list of free items, so we don't lose it if (isBehindFreeSpace(currentIndex)) { //Create the free item at the beginning of currentIndex, so it can be merged with the free space in front freeItemPosition = currentIndex; currentIndex += freeItemSize + AdditionalSpacePerItem; } else { //Create the free item behind currentIndex freeItemPosition = currentIndex + itemSize + AdditionalSpacePerItem; } setFreeSize(freeItemPosition, freeItemSize); insertFreeItem(freeItemPosition); } insertedAt = currentIndex; Q_ASSERT(( bool )m_freeItemCount == ( bool )m_largestFreeItem); } else { //We have to insert the item insertedAt = ItemRepositoryBucketSize - m_available; insertedAt += AdditionalSpacePerItem; //Room for the prepended follower-index m_available -= totalSize; } ifDebugLostSpace(Q_ASSERT(lostSpace() == totalSize); ) Q_ASSERT(!index || !followerIndex(index)); Q_ASSERT(!m_objectMap[localHash] || index); if (index) setFollowerIndex(index, insertedAt); setFollowerIndex(insertedAt, 0); if (m_objectMap[localHash] == 0) m_objectMap[localHash] = insertedAt; #ifdef DEBUG_CREATEITEM_EXTENTS char* borderBehind = m_data + insertedAt + (totalSize - AdditionalSpacePerItem); quint64 oldValueBehind = 0; if (m_available >= 8) { oldValueBehind = *( quint64* )borderBehind; *(( quint64* )borderBehind) = 0xfafafafafafafafaLLU; } #endif //Last thing we do, because createItem may recursively do even more transformation of the repository if (markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); request.createItem(reinterpret_cast(m_data + insertedAt)); if (markForReferenceCounting) disableDUChainReferenceCounting(m_data); #ifdef DEBUG_CREATEITEM_EXTENTS if (m_available >= 8) { //If this assertion triggers, then the item writes a bigger range than it advertised in Q_ASSERT(*(( quint64* )borderBehind) == 0xfafafafafafafafaLLU); *(( quint64* )borderBehind) = oldValueBehind; } #endif Q_ASSERT(itemFromIndex(insertedAt)->hash() == request.hash()); Q_ASSERT(itemFromIndex(insertedAt)->itemSize() == itemSize); ifDebugLostSpace(if (lostSpace()) qDebug() << "lost space:" << lostSpace(); Q_ASSERT(!lostSpace()); ) return insertedAt; } /// @param modulo Returns whether this bucket contains an item with (hash % modulo) == (item.hash % modulo) /// The default-parameter is the size of the next-bucket hash that is used by setNextBucketForHash and nextBucketForHash /// @note modulo MUST be a multiple of ObjectMapSize, because (b-a) | (x * h1) => (b-a) | h2, where a|b means a is a multiple of b. /// This this allows efficiently computing the clashes using the local object map hash. bool hasClashingItem(uint hash, uint modulo) { Q_ASSERT(modulo % ObjectMapSize == 0); m_lastUsed = 0; uint hashMod = hash % modulo; unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; if (currentIndex == 0) return false; while (currentIndex) { uint currentHash = itemFromIndex(currentIndex)->hash(); Q_ASSERT(currentHash % ObjectMapSize == localHash); if (currentHash % modulo == hashMod) return true; //Clash currentIndex = followerIndex(currentIndex); } return false; } void countFollowerIndexLengths(uint& usedSlots, uint& lengths, uint& slotCount, uint& longestInBucketFollowerChain) { for (uint a = 0; a < ObjectMapSize; ++a) { unsigned short currentIndex = m_objectMap[a]; ++slotCount; uint length = 0; if (currentIndex) { ++usedSlots; while (currentIndex) { ++length; ++lengths; currentIndex = followerIndex(currentIndex); } if (length > longestInBucketFollowerChain) { // qDebug() << "follower-chain at" << a << ":" << length; longestInBucketFollowerChain = length; } } } } //Returns whether the given item is reachabe within this bucket, through its hash. bool itemReachable(const Item* item, uint hash) const { unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while (currentIndex) { if (itemFromIndex(currentIndex) == item) return true; currentIndex = followerIndex(currentIndex); } return false; } template void deleteItem(unsigned short index, unsigned int hash, Repository& repository) { ifDebugLostSpace(Q_ASSERT(!lostSpace()); ) m_lastUsed = 0; prepareChange(); unsigned int size = itemFromIndex(index)->itemSize(); //Step 1: Remove the item from the data-structures that allow finding it: m_objectMap unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; unsigned short previousIndex = 0; //Fix the follower-link by setting the follower of the previous item to the next one, or updating m_objectMap while (currentIndex != index) { previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); //If this assertion triggers, the deleted item was not registered under the given hash Q_ASSERT(currentIndex); } Q_ASSERT(currentIndex == index); if (!previousIndex) //The item was directly in the object map m_objectMap[localHash] = followerIndex(index); else setFollowerIndex(previousIndex, followerIndex(index)); Item* item = const_cast(itemFromIndex(index)); if (markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); ItemRequest::destroy(item, repository); if (markForReferenceCounting) disableDUChainReferenceCounting(m_data); #ifndef QT_NO_DEBUG #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 800) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" #endif memset(item, 0, size); //For debugging, so we notice the data is wrong #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 800) #pragma GCC diagnostic pop #endif #endif if (m_monsterBucketExtent) { ///This is a monster-bucket. Make it completely empty again. Q_ASSERT(!m_available); m_available = ItemRepositoryBucketSize; //Items are always inserted into monster-buckets at a fixed position Q_ASSERT(currentIndex == AdditionalSpacePerItem); Q_ASSERT(m_objectMap[localHash] == 0); } else { ///Put the space into the free-set setFreeSize(index, size); //Try merging the created free item to other free items around, else add it into the free list insertFreeItem(index); if (m_freeItemCount == 1 && freeSize(m_largestFreeItem) + m_available == ItemRepositoryBucketSize) { //Everything has been deleted, there is only free space left. Make the bucket empty again, //so it can later also be used as a monster-bucket. m_available = ItemRepositoryBucketSize; m_freeItemCount = 0; m_largestFreeItem = 0; } } Q_ASSERT(( bool )m_freeItemCount == ( bool )m_largestFreeItem); ifDebugLostSpace(Q_ASSERT(!lostSpace()); ) #ifdef DEBUG_INCORRECT_DELETE //Make sure the item cannot be found any more { unsigned short localHash = hash % ObjectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while (currentIndex && currentIndex != index) { previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); } Q_ASSERT(!currentIndex); //The item must not be found } #endif // Q_ASSERT(canAllocateItem(size)); } ///@warning The returned item may be in write-protected memory, so never try doing a const_cast and changing some data /// If you need to change something, use dynamicItemFromIndex ///@warning When using multi-threading, mutex() must be locked as long as you use the returned data inline const Item* itemFromIndex(unsigned short index) const { m_lastUsed = 0; return reinterpret_cast(m_data + index); } bool isEmpty() const { return m_available == ItemRepositoryBucketSize; } ///Returns true if this bucket has no nextBucketForHash links bool noNextBuckets() const { for (int a = 0; a < NextBucketHashSize; ++a) if (m_nextBucketHash[a]) return false; return true; } uint available() const { return m_available; } uint usedMemory() const { return ItemRepositoryBucketSize - m_available; } template bool visitAllItems(Visitor& visitor) const { m_lastUsed = 0; for (uint a = 0; a < ObjectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while (currentIndex) { //Get the follower early, so there is no problems when the current //index is removed if (!visitor(reinterpret_cast(m_data + currentIndex))) return false; currentIndex = followerIndex(currentIndex); } } return true; } ///Returns whether something was changed template int finalCleanup(Repository& repository) { int changed = 0; while (m_dirty) { m_dirty = false; for (uint a = 0; a < ObjectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while (currentIndex) { //Get the follower early, so there is no problems when the current //index is removed const Item* item = reinterpret_cast(m_data + currentIndex); if (!ItemRequest::persistent(item)) { changed += item->itemSize(); deleteItem(currentIndex, item->hash(), repository); m_dirty = true; //Set to dirty so we re-iterate break; } currentIndex = followerIndex(currentIndex); } } } return changed; } unsigned short nextBucketForHash(uint hash) const { m_lastUsed = 0; return m_nextBucketHash[hash % NextBucketHashSize]; } void setNextBucketForHash(unsigned int hash, unsigned short bucket) { m_lastUsed = 0; prepareChange(); m_nextBucketHash[hash % NextBucketHashSize] = bucket; } uint freeItemCount() const { return m_freeItemCount; } short unsigned int totalFreeItemsSize() const { short unsigned int ret = 0; short unsigned int currentIndex = m_largestFreeItem; while (currentIndex) { ret += freeSize(currentIndex); currentIndex = followerIndex(currentIndex); } return ret; } //Size of the largest item that could be inserted into this bucket short unsigned int largestFreeSize() const { short unsigned int ret = 0; if (m_largestFreeItem) ret = freeSize(m_largestFreeItem); if (m_available > ( uint )(AdditionalSpacePerItem + ( uint )ret)) { ret = m_available - AdditionalSpacePerItem; Q_ASSERT(ret == (m_available - AdditionalSpacePerItem)); } return ret; } bool canAllocateItem(unsigned int size) const { short unsigned int currentIndex = m_largestFreeItem; while (currentIndex) { short unsigned int currentFree = freeSize(currentIndex); if (currentFree < size) return false; //Either we need an exact match, or 2 additional bytes to manage the resulting gap if (size == currentFree || currentFree - size >= AdditionalSpacePerItem + 2) return true; currentIndex = followerIndex(currentIndex); } return false; } void tick() const { ++m_lastUsed; } //How many ticks ago the item was last used int lastUsed() const { return m_lastUsed; } //Whether this bucket was changed since it was last stored bool changed() const { return m_changed; } void prepareChange() { m_changed = true; m_dirty = true; makeDataPrivate(); } bool dirty() const { return m_dirty; } ///Returns the count of following buckets that were merged onto this buckets data array int monsterBucketExtent() const { return m_monsterBucketExtent; } //Counts together the space that is neither accessible through m_objectMap nor through the free items uint lostSpace() { if (m_monsterBucketExtent) return 0; uint need = ItemRepositoryBucketSize - m_available; uint found = 0; for (uint a = 0; a < ObjectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while (currentIndex) { found += reinterpret_cast(m_data + currentIndex)->itemSize() + AdditionalSpacePerItem; currentIndex = followerIndex(currentIndex); } } uint currentIndex = m_largestFreeItem; while (currentIndex) { found += freeSize(currentIndex) + AdditionalSpacePerItem; currentIndex = followerIndex(currentIndex); } return need - found; } private: void makeDataPrivate() { if (m_mappedData == m_data) { short unsigned int* oldObjectMap = m_objectMap; short unsigned int* oldNextBucketHash = m_nextBucketHash; m_data = new char[ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize]; m_objectMap = new short unsigned int[ObjectMapSize]; m_nextBucketHash = new short unsigned int[NextBucketHashSize]; memcpy(m_data, m_mappedData, ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize); memcpy(m_objectMap, oldObjectMap, ObjectMapSize * sizeof(short unsigned int)); memcpy(m_nextBucketHash, oldNextBucketHash, NextBucketHashSize * sizeof(short unsigned int)); } } ///Merges the given index item, which must have a freeSize() set, to surrounding free items, and inserts the result. ///The given index itself should not be in the free items chain yet. ///Returns whether the item was inserted somewhere. void insertFreeItem(unsigned short index) { //If the item-size is fixed, we don't need to do any management. Just keep a list of free items. Items of other size will never be requested. if (!fixedItemSize) { unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; while (currentIndex) { Q_ASSERT(currentIndex != index); #ifndef QT_NO_DEBUG unsigned short currentFreeSize = freeSize(currentIndex); #endif ///@todo Achieve this without iterating through all items in the bucket(This is very slow) //Merge behind index if (currentIndex == index + freeSize(index) + AdditionalSpacePerItem) { //Remove currentIndex from the free chain, since it's merged backwards into index if (previousIndex && followerIndex(currentIndex)) Q_ASSERT(freeSize(previousIndex) >= freeSize(followerIndex(currentIndex))); if (previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //One was removed //currentIndex is directly behind index, touching its space. Merge them. setFreeSize(index, freeSize(index) + AdditionalSpacePerItem + freeSize(currentIndex)); //Recurse to do even more merging insertFreeItem(index); return; } //Merge before index if (index == currentIndex + freeSize(currentIndex) + AdditionalSpacePerItem) { if (previousIndex && followerIndex(currentIndex)) Q_ASSERT(freeSize(previousIndex) >= freeSize(followerIndex(currentIndex))); //Remove currentIndex from the free chain, since insertFreeItem wants //it not to be in the chain, and it will be inserted in another place if (previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //One was removed //index is directly behind currentIndex, touching its space. Merge them. setFreeSize(currentIndex, freeSize(currentIndex) + AdditionalSpacePerItem + freeSize(index)); //Recurse to do even more merging insertFreeItem(currentIndex); return; } previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); #ifndef QT_NO_DEBUG if (currentIndex) Q_ASSERT(freeSize(currentIndex) <= currentFreeSize); #endif } } insertToFreeChain(index); } ///Only inserts the item in the correct position into the free chain. index must not be in the chain yet. void insertToFreeChain(unsigned short index) { if (!fixedItemSize) { ///@todo Use some kind of tree to find the correct position in the chain(This is very slow) //Insert the free item into the chain opened by m_largestFreeItem unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; unsigned short size = freeSize(index); while (currentIndex && freeSize(currentIndex) > size) { Q_ASSERT(currentIndex != index); //must not be in the chain yet previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); } if (currentIndex) Q_ASSERT(freeSize(currentIndex) <= size); setFollowerIndex(index, currentIndex); if (previousIndex) { Q_ASSERT(freeSize(previousIndex) >= size); setFollowerIndex(previousIndex, index); } else //This item is larger than all already registered free items, or there are none. m_largestFreeItem = index; } else { Q_ASSERT(freeSize(index) == fixedItemSize); //When all items have the same size, just prepent to the front. setFollowerIndex(index, m_largestFreeItem); m_largestFreeItem = index; } ++m_freeItemCount; } /// Returns true if the given index is right behind free space, and thus can be merged to the free space. bool isBehindFreeSpace(unsigned short index) const { // TODO: Without iteration! unsigned short currentIndex = m_largestFreeItem; while (currentIndex) { if (index == currentIndex + freeSize(currentIndex) + AdditionalSpacePerItem) return true; currentIndex = followerIndex(currentIndex); } return false; } /// @param index the index of an item @return The index of the next item in the chain of items with a same local hash, or zero inline unsigned short followerIndex(unsigned short index) const { Q_ASSERT(index >= 2); return *reinterpret_cast(m_data + (index - 2)); } void setFollowerIndex(unsigned short index, unsigned short follower) { Q_ASSERT(index >= 2); *reinterpret_cast(m_data + (index - 2)) = follower; } // Only returns the current value if the item is actually free inline unsigned short freeSize(unsigned short index) const { return *reinterpret_cast(m_data + index); } //Convenience function to set the free-size, only for freed items void setFreeSize(unsigned short index, unsigned short size) { *reinterpret_cast(m_data + index) = size; } int m_monsterBucketExtent = 0; //If this is a monster-bucket, this contains the count of follower-buckets that belong to this one unsigned int m_available = 0; char* m_data = nullptr; //Structure of the data: (2 byte), (item.size() byte) char* m_mappedData = nullptr; //Read-only memory-mapped data. If this equals m_data, m_data must not be written short unsigned int* m_objectMap = nullptr; //Points to the first object in m_data with (hash % ObjectMapSize) == index. Points to the item itself, so subtract 1 to get the pointer to the next item with same local hash. short unsigned int m_largestFreeItem = 0; //Points to the largest item that is currently marked as free, or zero. That one points to the next largest one through followerIndex unsigned int m_freeItemCount = 0; unsigned short* m_nextBucketHash = nullptr; bool m_dirty = false; //Whether the data was changed since the last finalCleanup bool m_changed = false; //Whether this bucket was changed since it was last stored to disk mutable int m_lastUsed = 0; //How many ticks ago this bucket was last accessed }; template struct Locker //This is a dummy that does nothing { template explicit Locker(const T& /*t*/) { } }; template <> struct Locker { explicit Locker(QMutex* mutex) : m_mutex(mutex) { m_mutex->lock(); } ~Locker() { m_mutex->unlock(); } QMutex* m_mutex; }; ///This object needs to be kept alive as long as you change the contents of an item ///stored in the repository. It is needed to correctly track the reference counting ///within disk-storage. ///@warning You can not freely copy this around, when you create a copy, the copy source /// becomes invalid template class DynamicItem { public: DynamicItem(Item* i, void* start, uint size) : m_item(i) , m_start(start) { if (markForReferenceCounting) enableDUChainReferenceCounting(m_start, size); // qDebug() << "enabling" << i << "to" << (void*)(((char*)i)+size); } ~DynamicItem() { if (m_start) { // qDebug() << "destructor-disabling" << m_item; if (markForReferenceCounting) disableDUChainReferenceCounting(m_start); } } DynamicItem(const DynamicItem& rhs) : m_item(rhs.m_item) , m_start(rhs.m_start) { // qDebug() << "stealing" << m_item; Q_ASSERT(rhs.m_start); rhs.m_start = nullptr; } Item* operator->() { return m_item; } Item* m_item; private: mutable void* m_start; DynamicItem& operator=(const DynamicItem&); }; ///@tparam Item See ExampleItem ///@tparam ItemRequest See ExampleReqestItem ///@tparam fixedItemSize When this is true, all inserted items must have the same size. /// This greatly simplifies and speeds up the task of managing free items within the buckets. ///@tparam markForReferenceCounting Whether the data within the repository should be marked for reference-counting. /// This costs a bit of performance, but must be enabled if there may be data in the repository /// that does on-disk reference counting, like IndexedString, IndexedIdentifier, etc. ///@tparam threadSafe Whether class access should be thread-safe. Disabling this is dangerous when you do multi-threading. /// You have to make sure that mutex() is locked whenever the repository is accessed. template class ItemRepository : public AbstractItemRepository { - typedef Locker ThisLocker; + using ThisLocker = Locker; - typedef Bucket MyBucket; + using MyBucket = Bucket; enum { //Must be a multiple of Bucket::ObjectMapSize, so Bucket::hasClashingItem can be computed //Must also be a multiple of Bucket::NextBucketHashSize, for the same reason.(Currently those are same) bucketHashSize = (targetBucketHashSize / MyBucket::ObjectMapSize) * MyBucket::ObjectMapSize }; enum { BucketStartOffset = sizeof(uint) * 7 + sizeof(short unsigned int) * bucketHashSize //Position in the data where the bucket array starts }; public: ///@param registry May be zero, then the repository will not be registered at all. Else, the repository will register itself to that registry. /// If this is zero, you have to care about storing the data using store() and/or close() by yourself. It does not happen automatically. /// For the global standard registry, the storing/loading is triggered from within duchain, so you don't need to care about it. explicit ItemRepository(const QString& repositoryName, ItemRepositoryRegistry* registry = & globalItemRepositoryRegistry(), uint repositoryVersion = 1, AbstractRepositoryManager* manager = nullptr) : m_ownMutex(QMutex::Recursive) , m_mutex(&m_ownMutex) , m_repositoryName(repositoryName) , m_registry(registry) , m_file(nullptr) , m_dynamicFile(nullptr) , m_repositoryVersion(repositoryVersion) , m_manager(manager) { m_unloadingEnabled = true; m_metaDataChanged = true; m_buckets.resize(10); m_buckets.fill(nullptr); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); m_statBucketHashClashes = m_statItemCount = 0; m_currentBucket = 1; //Skip the first bucket, we won't use it so we have the zero indices for special purposes if (m_registry) m_registry->registerRepository(this, m_manager); } ~ItemRepository() override { if (m_registry) m_registry->unRegisterRepository(this); close(); } ///Unloading of buckets is enabled by default. Use this to disable it. When unloading is enabled, the data ///gotten from must only itemFromIndex must not be used for a long time. void setUnloadingEnabled(bool enabled) { m_unloadingEnabled = enabled; } ///Returns the index for the given item. If the item is not in the repository yet, it is inserted. ///The index can never be zero. Zero is reserved for your own usage as invalid ///@param request Item to retrieve the index from unsigned int index(const ItemRequest& request) { ThisLocker lock(m_mutex); const uint hash = request.hash(); const uint size = request.itemSize(); // Bucket indexes tracked while walking the bucket chain for this request hash unsigned short bucketInChainWithSpace = 0; unsigned short lastBucketWalked = 0; const ushort foundIndexInBucket = walkBucketChain(hash, [&](ushort bucketIdx, const MyBucket* bucketPtr) { lastBucketWalked = bucketIdx; const ushort found = bucketPtr->findIndex(request); if (!found && !bucketInChainWithSpace && bucketPtr->canAllocateItem(size)) { bucketInChainWithSpace = bucketIdx; } return found; }); if (foundIndexInBucket) { // 'request' is already present, return the existing index return createIndex(lastBucketWalked, foundIndexInBucket); } /* * Disclaimer: Writer of comment != writer of code, believe with caution * * Requested item does not yet exist. Need to find a place to put it... * * First choice is to place it in an existing bucket in the chain for the request hash * Second choice is to find an existing bucket anywhere with enough space * Otherwise use m_currentBucket (the latest unused bucket) * * If the chosen bucket fails to allocate the item, merge buckets to create a monster (dragon?) * * Finally, if the first option failed or the selected bucket failed to allocate, add the * bucket which the item ended up in to the chain of buckets for the request's hash */ m_metaDataChanged = true; const bool pickedBucketInChain = bucketInChainWithSpace; int useBucket = bucketInChainWithSpace; int reOrderFreeSpaceBucketIndex = -1; if (!pickedBucketInChain) { //Try finding an existing bucket with deleted space to store the data into for (int a = 0; a < m_freeSpaceBuckets.size(); ++a) { MyBucket* bucketPtr = bucketForIndex(m_freeSpaceBuckets[a]); Q_ASSERT(bucketPtr->largestFreeSize()); if (bucketPtr->canAllocateItem(size)) { //The item fits into the bucket. useBucket = m_freeSpaceBuckets[a]; reOrderFreeSpaceBucketIndex = a; break; } } if (!useBucket) { useBucket = m_currentBucket; } } else { reOrderFreeSpaceBucketIndex = m_freeSpaceBuckets.indexOf(useBucket); } //The item isn't in the repository yet, find a new bucket for it while (1) { if (useBucket >= m_buckets.size()) { if (m_buckets.size() >= 0xfffe) { //We have reserved the last bucket index 0xffff for special purposes //the repository has overflown. qWarning() << "Found no room for an item in" << m_repositoryName << "size of the item:" << request.itemSize(); return 0; } else { //Allocate new buckets m_buckets.resize(m_buckets.size() + 10); } } MyBucket* bucketPtr = m_buckets.at(useBucket); if (!bucketPtr) { initializeBucket(useBucket); bucketPtr = m_buckets.at(useBucket); } ENSURE_REACHABLE(useBucket); Q_ASSERT_X(!bucketPtr->findIndex( request), Q_FUNC_INFO, "found item in unexpected bucket, ensure your ItemRequest::equals method is correct. Note: For custom AbstractType's e.g. ensure you have a proper equals() override"); unsigned short indexInBucket = bucketPtr->index(request, size); //If we could not allocate the item in an empty bucket, then we need to create a monster-bucket that //can hold the data. if (bucketPtr->isEmpty() && !indexInBucket) { ///@todo Move this compound statement into an own function uint totalSize = size + MyBucket::AdditionalSpacePerItem; Q_ASSERT((totalSize > ItemRepositoryBucketSize)); useBucket = 0; //The item did not fit in, we need a monster-bucket(Merge consecutive buckets) ///Step one: Search whether we can merge multiple empty buckets in the free-list into one monster-bucket int rangeStart = -1; int rangeEnd = -1; for (int a = 0; a < m_freeSpaceBuckets.size(); ++a) { MyBucket* bucketPtr = bucketForIndex(m_freeSpaceBuckets[a]); if (bucketPtr->isEmpty()) { //This bucket is a candidate for monster-bucket merging int index = ( int )m_freeSpaceBuckets[a]; if (rangeEnd != index) { rangeStart = index; rangeEnd = index + 1; } else { ++rangeEnd; } if (rangeStart != rangeEnd) { uint extent = rangeEnd - rangeStart - 1; uint totalAvailableSpace = bucketForIndex(rangeStart)->available() + MyBucket::DataSize* (rangeEnd - rangeStart - 1); if (totalAvailableSpace > totalSize) { Q_ASSERT(extent); ///We can merge these buckets into one monster-bucket that can hold the data Q_ASSERT(( uint )m_freeSpaceBuckets[a - extent] == ( uint )rangeStart); m_freeSpaceBuckets.remove(a - extent, extent + 1); useBucket = rangeStart; convertMonsterBucket(rangeStart, extent); break; } } } } if (!useBucket) { //Create a new monster-bucket at the end of the data int needMonsterExtent = (totalSize - ItemRepositoryBucketSize) / MyBucket::DataSize + 1; Q_ASSERT(needMonsterExtent); if (m_currentBucket + needMonsterExtent + 1 > m_buckets.size()) { m_buckets.resize(m_buckets.size() + 10 + needMonsterExtent + 1); } useBucket = m_currentBucket; convertMonsterBucket(useBucket, needMonsterExtent); m_currentBucket += 1 + needMonsterExtent; Q_ASSERT(m_currentBucket < ItemRepositoryBucketLimit); Q_ASSERT(m_buckets[m_currentBucket - 1 - needMonsterExtent] && m_buckets[m_currentBucket - 1 - needMonsterExtent]->monsterBucketExtent() == needMonsterExtent); } Q_ASSERT(useBucket); bucketPtr = bucketForIndex(useBucket); indexInBucket = bucketPtr->index(request, size); Q_ASSERT(indexInBucket); } if (indexInBucket) { ++m_statItemCount; const int previousBucketNumber = lastBucketWalked; unsigned short* const bucketHashPosition = m_firstBucketForHash + (hash % bucketHashSize); if (!(*bucketHashPosition)) { Q_ASSERT(!previousBucketNumber); (*bucketHashPosition) = useBucket; ENSURE_REACHABLE(useBucket); } else if (!pickedBucketInChain && previousBucketNumber && previousBucketNumber != useBucket) { //Should happen rarely ++m_statBucketHashClashes; ///Debug: Detect infinite recursion ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, previousBucketNumber)); ) //Find the position where the paths of useBucket and *bucketHashPosition intersect, and insert useBucket //there. That way, we don't create loops. QPair intersect = hashChainIntersection(*bucketHashPosition, useBucket, hash); Q_ASSERT(m_buckets[previousBucketNumber]->nextBucketForHash(hash) == 0); if (!intersect.second) { ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket)); ) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(useBucket, hash, previousBucketNumber)); ) Q_ASSERT(m_buckets[previousBucketNumber]->nextBucketForHash(hash) == 0); m_buckets[previousBucketNumber]->setNextBucketForHash(hash, useBucket); ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(previousBucketNumber); ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, useBucket)); ) } else if (intersect.first) { ifDebugInfiniteRecursion(Q_ASSERT(bucketForIndex(intersect.first)->nextBucketForHash(hash) == intersect.second); ) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket)); ) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.second)); ) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.first)); ) ifDebugInfiniteRecursion(Q_ASSERT(bucketForIndex(intersect.first)->nextBucketForHash(hash) == intersect.second); ) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(useBucket, hash, intersect.second)); ) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(useBucket, hash, intersect.first)); ) bucketForIndex(intersect.first)->setNextBucketForHash(hash, useBucket); ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(intersect.second); ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, useBucket)); ) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.second)); ) } else { //State: intersect.first == 0 && intersect.second != 0. This means that whole compleet //chain opened by *bucketHashPosition with the hash-value is also following useBucket, //so useBucket can just be inserted at the top ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket)); ) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(useBucket, hash, *bucketHashPosition)); ) unsigned short oldStart = *bucketHashPosition; *bucketHashPosition = useBucket; ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(oldStart); Q_UNUSED(oldStart); } } if (reOrderFreeSpaceBucketIndex != -1) updateFreeSpaceOrder(reOrderFreeSpaceBucketIndex); return createIndex(useBucket, indexInBucket); } else { //This should never happen when we picked a bucket for re-use Q_ASSERT(!pickedBucketInChain); Q_ASSERT(reOrderFreeSpaceBucketIndex == -1); Q_ASSERT(useBucket == m_currentBucket); if (!bucketForIndex(useBucket)->isEmpty()) putIntoFreeList(useBucket, bucketPtr); ++m_currentBucket; Q_ASSERT(m_currentBucket < ItemRepositoryBucketLimit); useBucket = m_currentBucket; } } //We haven't found a bucket that already contains the item, so append it to the current bucket qWarning() << "Found no bucket for an item in" << m_repositoryName; return 0; } ///Returns zero if the item is not in the repository yet unsigned int findIndex(const ItemRequest& request) { ThisLocker lock(m_mutex); return walkBucketChain(request.hash(), [this, &request](ushort bucketIdx, const MyBucket* bucketPtr) { const ushort indexInBucket = bucketPtr->findIndex(request); return indexInBucket ? createIndex(bucketIdx, indexInBucket) : 0u; }); } ///Deletes the item from the repository. void deleteItem(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); m_metaDataChanged = true; const uint hash = itemFromIndex(index)->hash(); const ushort bucket = (index >> 16); //Apart from removing the item itself, we may have to recreate the nextBucketForHash link, so we need the previous bucket MyBucket* previousBucketPtr = nullptr; MyBucket* const bucketPtr = walkBucketChain(hash, [bucket, &previousBucketPtr](ushort bucketIdx, MyBucket* bucketPtr) -> MyBucket* { if (bucket != bucketIdx) { previousBucketPtr = bucketPtr; return nullptr; } return bucketPtr; // found bucket, stop looking }); //Make sure the index was reachable through the hash chain Q_ASSERT(bucketPtr); --m_statItemCount; bucketPtr->deleteItem(index, hash, *this); /** * Now check whether the link root/previousBucketNumber -> bucket is still needed. */ if (!previousBucketPtr) { // This bucket is linked in the m_firstBucketForHash array, find the next clashing bucket in the chain // There may be items in the chain that clash only with MyBucket::NextBucketHashSize, skipped here m_firstBucketForHash[hash % bucketHashSize] = walkBucketChain(hash, [hash](ushort bucketIdx, MyBucket* bucketPtr) { if (bucketPtr->hasClashingItem(hash, bucketHashSize)) { return bucketIdx; } return static_cast(0); }); } else if (!bucketPtr->hasClashingItem(hash, MyBucket::NextBucketHashSize)) { // TODO: Skip clashing items reachable from m_firstBucketForHash // (see note in usePermissiveModuloWhenRemovingClashLinks() test) ENSURE_REACHABLE(bucket); previousBucketPtr->setNextBucketForHash(hash, bucketPtr->nextBucketForHash(hash)); Q_ASSERT(m_buckets[bucketPtr->nextBucketForHash(hash)] != previousBucketPtr); } ENSURE_REACHABLE(bucket); if (bucketPtr->monsterBucketExtent()) { //Convert the monster-bucket back to multiple normal buckets, and put them into the free list uint newBuckets = bucketPtr->monsterBucketExtent() + 1; Q_ASSERT(bucketPtr->isEmpty()); if (!previousBucketPtr) { // see https://bugs.kde.org/show_bug.cgi?id=272408 // the monster bucket will be deleted and new smaller ones created // the next bucket for this hash is invalid anyways as done above // but calling the below unconditionally leads to other issues... bucketPtr->setNextBucketForHash(hash, 0); } convertMonsterBucket(bucket, 0); for (uint created = bucket; created < bucket + newBuckets; ++created) { putIntoFreeList(created, bucketForIndex(created)); #ifdef DEBUG_MONSTERBUCKETS Q_ASSERT(m_freeSpaceBuckets.indexOf(created) != -1); #endif } } else { putIntoFreeList(bucket, bucketPtr); } } - typedef DynamicItem MyDynamicItem; + using MyDynamicItem = DynamicItem; ///This returns an editable version of the item. @warning: Never change an entry that affects the hash, ///or the equals(..) function. That would completely destroy the consistency. ///@param index The index. It must be valid(match an existing item), and nonzero. ///@warning If you use this, make sure you lock mutex() before calling, /// and hold it until you're ready using/changing the data.. MyDynamicItem dynamicItemFromIndex(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets.at(bucket); if (!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets.at(bucket); } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; return MyDynamicItem(const_cast(bucketPtr->itemFromIndex(indexInBucket)), bucketPtr->data(), bucketPtr->dataSize()); } ///This returns an editable version of the item. @warning: Never change an entry that affects the hash, ///or the equals(..) function. That would completely destroy the consistency. ///@param index The index. It must be valid(match an existing item), and nonzero. ///@warning If you use this, make sure you lock mutex() before calling, /// and hold it until you're ready using/changing the data.. ///@warning If you change contained complex items that depend on reference-counting, you /// must use dynamicItemFromIndex(..) instead of dynamicItemFromIndexSimple(..) Item* dynamicItemFromIndexSimple(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets.at(bucket); if (!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets.at(bucket); } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; return const_cast(bucketPtr->itemFromIndex(indexInBucket)); } ///@param index The index. It must be valid(match an existing item), and nonzero. const Item* itemFromIndex(unsigned int index) const { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); const MyBucket* bucketPtr = m_buckets.at(bucket); if (!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets.at(bucket); } unsigned short indexInBucket = index & 0xffff; return bucketPtr->itemFromIndex(indexInBucket); } struct Statistics { Statistics() { } uint loadedBuckets = -1; uint currentBucket = -1; uint usedMemory = -1; uint loadedMonsterBuckets = -1; uint usedSpaceForBuckets = -1; uint freeSpaceInBuckets = -1; uint lostSpace = -1; uint freeUnreachableSpace = -1; uint hashClashedItems = -1; uint totalItems = -1; uint emptyBuckets; uint hashSize = -1; //How big the hash is uint hashUse = -1; //How many slots in the hash are used uint averageInBucketHashSize = -1; uint averageInBucketUsedSlotCount = -1; float averageInBucketSlotChainLength = -1; uint longestInBucketChain = -1; uint longestNextBucketChain = -1; uint totalBucketFollowerSlots = -1; //Total count of used slots in the nextBucketForHash structure float averageNextBucketForHashSequenceLength = -1; //Average sequence length of a nextBucketForHash sequence(If not empty) QString print() const { QString ret; ret += QStringLiteral("loaded buckets: %1 current bucket: %2 used memory: %3 loaded monster buckets: %4").arg( loadedBuckets).arg(currentBucket).arg(usedMemory).arg(loadedMonsterBuckets); ret += QStringLiteral("\nbucket hash clashed items: %1 total items: %2").arg(hashClashedItems).arg( totalItems); ret += QStringLiteral("\nused space for buckets: %1 free space in buckets: %2 lost space: %3").arg( usedSpaceForBuckets).arg(freeSpaceInBuckets).arg(lostSpace); ret += QStringLiteral("\nfree unreachable space: %1 empty buckets: %2").arg(freeUnreachableSpace).arg( emptyBuckets); ret += QStringLiteral("\nhash size: %1 hash slots used: %2").arg(hashSize).arg(hashUse); ret += QStringLiteral( "\naverage in-bucket hash size: %1 average in-bucket used hash slot count: %2 average in-bucket slot chain length: %3 longest in-bucket follower chain: %4") .arg(averageInBucketHashSize).arg(averageInBucketUsedSlotCount).arg(averageInBucketSlotChainLength). arg( longestInBucketChain); ret += QStringLiteral( "\ntotal count of used next-bucket-for-hash slots: %1 average next-bucket-for-hash sequence length: %2 longest next-bucket chain: %3") .arg(totalBucketFollowerSlots).arg(averageNextBucketForHashSequenceLength).arg(longestNextBucketChain); return ret; } operator QString() const { return print(); } }; QString printStatistics() const override { return statistics().print(); } Statistics statistics() const { Statistics ret; uint loadedBuckets = 0; for (int a = 0; a < m_buckets.size(); ++a) if (m_buckets[a]) ++loadedBuckets; #ifdef DEBUG_MONSTERBUCKETS for (int a = 0; a < m_freeSpaceBuckets.size(); ++a) { if (a > 0) { uint prev = a - 1; uint prevLargestFree = bucketForIndex(m_freeSpaceBuckets[prev])->largestFreeSize(); uint largestFree = bucketForIndex(m_freeSpaceBuckets[a])->largestFreeSize(); Q_ASSERT((prevLargestFree < largestFree) || (prevLargestFree == largestFree && m_freeSpaceBuckets[prev] < m_freeSpaceBuckets[a])); } } #endif ret.hashSize = bucketHashSize; ret.hashUse = 0; for (uint a = 0; a < bucketHashSize; ++a) if (m_firstBucketForHash[a]) ++ret.hashUse; ret.emptyBuckets = 0; uint loadedMonsterBuckets = 0; for (int a = 0; a < m_buckets.size(); ++a) if (m_buckets[a] && m_buckets[a]->monsterBucketExtent()) loadedMonsterBuckets += m_buckets[a]->monsterBucketExtent() + 1; uint usedBucketSpace = MyBucket::DataSize* m_currentBucket; uint freeBucketSpace = 0, freeUnreachableSpace = 0; uint lostSpace = 0; //Should be zero, else something is wrong uint totalInBucketHashSize = 0, totalInBucketUsedSlotCount = 0, totalInBucketChainLengths = 0; uint bucketCount = 0; ret.totalBucketFollowerSlots = 0; ret.averageNextBucketForHashSequenceLength = 0; ret.longestNextBucketChain = 0; ret.longestInBucketChain = 0; for (int a = 1; a < m_currentBucket + 1; ++a) { MyBucket* bucket = bucketForIndex(a); if (bucket) { ++bucketCount; bucket->countFollowerIndexLengths(totalInBucketUsedSlotCount, totalInBucketChainLengths, totalInBucketHashSize, ret.longestInBucketChain); for (uint aa = 0; aa < MyBucket::NextBucketHashSize; ++aa) { uint length = 0; uint next = bucket->nextBucketForHash(aa); if (next) { ++ret.totalBucketFollowerSlots; while (next) { ++length; ++ret.averageNextBucketForHashSequenceLength; next = bucketForIndex(next)->nextBucketForHash(aa); } } if (length > ret.longestNextBucketChain) { // qDebug() << "next-bucket-chain in" << a << aa << ":" << length; ret.longestNextBucketChain = length; } } uint bucketFreeSpace = bucket->totalFreeItemsSize() + bucket->available(); freeBucketSpace += bucketFreeSpace; if (m_freeSpaceBuckets.indexOf(a) == -1) freeUnreachableSpace += bucketFreeSpace; if (bucket->isEmpty()) { ++ret.emptyBuckets; Q_ASSERT(!bucket->monsterBucketExtent()); #ifdef DEBUG_MONSTERBUCKETS Q_ASSERT(m_freeSpaceBuckets.contains(a)); #endif } lostSpace += bucket->lostSpace(); a += bucket->monsterBucketExtent(); } } if (ret.totalBucketFollowerSlots) ret.averageNextBucketForHashSequenceLength /= ret.totalBucketFollowerSlots; ret.loadedBuckets = loadedBuckets; ret.currentBucket = m_currentBucket; ret.usedMemory = usedMemory(); ret.loadedMonsterBuckets = loadedMonsterBuckets; ret.hashClashedItems = m_statBucketHashClashes; ret.totalItems = m_statItemCount; ret.usedSpaceForBuckets = usedBucketSpace; ret.freeSpaceInBuckets = freeBucketSpace; ret.lostSpace = lostSpace; ret.freeUnreachableSpace = freeUnreachableSpace; ret.averageInBucketHashSize = totalInBucketHashSize / bucketCount; ret.averageInBucketUsedSlotCount = totalInBucketUsedSlotCount / bucketCount; ret.averageInBucketSlotChainLength = float( totalInBucketChainLengths ) / totalInBucketUsedSlotCount; //If m_statBucketHashClashes is high, the bucket-hash needs to be bigger return ret; } uint usedMemory() const { uint used = 0; for (int a = 0; a < m_buckets.size(); ++a) { if (m_buckets[a]) { used += m_buckets[a]->usedMemory(); } } return used; } ///This can be used to safely iterate through all items in the repository ///@param visitor Should be an instance of a class that has a bool operator()(const Item*) member function, /// that returns whether more items are wanted. ///@param onlyInMemory If this is true, only items are visited that are currently in memory. template void visitAllItems(Visitor& visitor, bool onlyInMemory = false) const { ThisLocker lock(m_mutex); for (int a = 1; a <= m_currentBucket; ++a) { if (!onlyInMemory || m_buckets.at(a)) { if (bucketForIndex(a) && !bucketForIndex(a)->visitAllItems(visitor)) return; } } } ///Synchronizes the state on disk to the one in memory, and does some memory-management. ///Should be called on a regular basis. Can be called centrally from the global item repository registry. void store() override { QMutexLocker lock(m_mutex); if (m_file) { if (!m_file->open(QFile::ReadWrite) || !m_dynamicFile->open(QFile::ReadWrite)) { qFatal("cannot re-open repository file for storing"); return; } for (int a = 0; a < m_buckets.size(); ++a) { if (m_buckets[a]) { if (m_buckets[a]->changed()) { storeBucket(a); } if (m_unloadingEnabled) { const int unloadAfterTicks = 2; if (m_buckets[a]->lastUsed() > unloadAfterTicks) { delete m_buckets[a]; m_buckets[a] = nullptr; } else { m_buckets[a]->tick(); } } } } if (m_metaDataChanged) { Q_ASSERT(m_dynamicFile); m_file->seek(0); m_file->write(( char* )&m_repositoryVersion, sizeof(uint)); uint hashSize = bucketHashSize; m_file->write(( char* )&hashSize, sizeof(uint)); uint itemRepositoryVersion = staticItemRepositoryVersion(); m_file->write(( char* )&itemRepositoryVersion, sizeof(uint)); m_file->write(( char* )&m_statBucketHashClashes, sizeof(uint)); m_file->write(( char* )&m_statItemCount, sizeof(uint)); const uint bucketCount = static_cast(m_buckets.size()); m_file->write(( char* )&bucketCount, sizeof(uint)); m_file->write(( char* )&m_currentBucket, sizeof(uint)); m_file->write(( char* )m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); Q_ASSERT(m_file->pos() == BucketStartOffset); m_dynamicFile->seek(0); const uint freeSpaceBucketsSize = static_cast(m_freeSpaceBuckets.size()); m_dynamicFile->write(( char* )&freeSpaceBucketsSize, sizeof(uint)); m_dynamicFile->write(( char* )m_freeSpaceBuckets.data(), sizeof(uint) * freeSpaceBucketsSize); } //To protect us from inconsistency due to crashes. flush() is not enough. We need to close. m_file->close(); m_dynamicFile->close(); Q_ASSERT(!m_file->isOpen()); Q_ASSERT(!m_dynamicFile->isOpen()); } } ///This mutex is used for the thread-safe locking when threadSafe is true. Even if threadSafe is false, it is ///always locked before storing to or loading from disk. ///@warning If threadSafe is false, and you sometimes call store() from within another thread(As happens in duchain), /// you must always make sure that this mutex is locked before you access this repository. /// Else you will get crashes and inconsistencies. /// In KDevelop This means: Make sure you _always_ lock this mutex before accessing the repository. QMutex* mutex() const { return m_mutex; } ///With this, you can replace the internal mutex with another one. void setMutex(QMutex* mutex) { m_mutex = mutex; } QString repositoryName() const override { return m_repositoryName; } private: uint createIndex(ushort bucketIndex, ushort indexInBucket) { //Combine the index in the bucket, and the bucket number into one index const uint index = (bucketIndex << 16) + indexInBucket; verifyIndex(index); return index; } /** * Walks through all buckets clashing with @p hash * * Will return the value returned by the lambda, returning early if truthy */ template auto walkBucketChain(unsigned int hash, const Visitor& visitor) const->decltype(visitor(0, nullptr)) { unsigned short bucketIndex = m_firstBucketForHash[hash % bucketHashSize]; while (bucketIndex) { auto* bucketPtr = m_buckets.at(bucketIndex); if (!bucketPtr) { initializeBucket(bucketIndex); bucketPtr = m_buckets.at(bucketIndex); } if (auto visitResult = visitor(bucketIndex, bucketPtr)) { return visitResult; } bucketIndex = bucketPtr->nextBucketForHash(hash); } return {}; // clazy:exclude=returning-void-expression } ///Makes sure the order within m_freeSpaceBuckets is correct, after largestFreeSize has been changed for m_freeSpaceBuckets[index]. ///If too few space is free within the given bucket, it is removed from m_freeSpaceBuckets. void updateFreeSpaceOrder(uint index) { m_metaDataChanged = true; unsigned int* freeSpaceBuckets = m_freeSpaceBuckets.data(); Q_ASSERT(index < static_cast(m_freeSpaceBuckets.size())); MyBucket* bucketPtr = bucketForIndex(freeSpaceBuckets[index]); unsigned short largestFreeSize = bucketPtr->largestFreeSize(); if (largestFreeSize == 0 || (bucketPtr->freeItemCount() <= MyBucket::MaxFreeItemsForHide && largestFreeSize <= MyBucket::MaxFreeSizeForHide)) { //Remove the item from freeSpaceBuckets m_freeSpaceBuckets.remove(index); } else { while (1) { int prev = index - 1; int next = index + 1; if (prev >= 0 && (bucketForIndex(freeSpaceBuckets[prev])->largestFreeSize() > largestFreeSize || (bucketForIndex(freeSpaceBuckets[prev])->largestFreeSize() == largestFreeSize && freeSpaceBuckets[index] < freeSpaceBuckets[prev])) ) { //This item should be behind the successor, either because it has a lower largestFreeSize, or because the index is lower uint oldPrevValue = freeSpaceBuckets[prev]; freeSpaceBuckets[prev] = freeSpaceBuckets[index]; freeSpaceBuckets[index] = oldPrevValue; index = prev; } else if (next < m_freeSpaceBuckets.size() && (bucketForIndex(freeSpaceBuckets[next])->largestFreeSize() < largestFreeSize || (bucketForIndex(freeSpaceBuckets[next])->largestFreeSize() == largestFreeSize && freeSpaceBuckets[index] > freeSpaceBuckets[next]))) { //This item should be behind the successor, either because it has a higher largestFreeSize, or because the index is higher uint oldNextValue = freeSpaceBuckets[next]; freeSpaceBuckets[next] = freeSpaceBuckets[index]; freeSpaceBuckets[index] = oldNextValue; index = next; } else { break; } } } } ///Does conversion from monster-bucket to normal bucket and from normal bucket to monster-bucket ///The bucket @param bucketNumber must already be loaded and empty. the "extent" buckets behind must also be loaded, ///and also be empty. ///The created buckets are not registered anywhere. When converting from monster-bucket to normal bucket, ///oldExtent+1 normal buckets are created, that must be registered somewhere. ///@warning During conversion, all the touched buckets are deleted and re-created ///@param extent When this is zero, the bucket is converted from monster-bucket to normal bucket. /// When it is nonzero, it is converted to a monster-bucket. MyBucket* convertMonsterBucket(int bucketNumber, int extent) { Q_ASSERT(bucketNumber); MyBucket* bucketPtr = m_buckets.at(bucketNumber); if (!bucketPtr) { initializeBucket(bucketNumber); bucketPtr = m_buckets.at(bucketNumber); } if (extent) { //Convert to monster-bucket #ifdef DEBUG_MONSTERBUCKETS for (int index = bucketNumber; index < bucketNumber + 1 + extent; ++index) { Q_ASSERT(bucketPtr->isEmpty()); Q_ASSERT(!bucketPtr->monsterBucketExtent()); Q_ASSERT(m_freeSpaceBuckets.indexOf(index) == -1); } #endif for (int index = bucketNumber; index < bucketNumber + 1 + extent; ++index) deleteBucket(index); m_buckets[bucketNumber] = new MyBucket(); m_buckets[bucketNumber]->initialize(extent); #ifdef DEBUG_MONSTERBUCKETS for (uint index = bucketNumber + 1; index < bucketNumber + 1 + extent; ++index) { Q_ASSERT(!m_buckets[index]); } #endif } else { Q_ASSERT(bucketPtr->monsterBucketExtent()); Q_ASSERT(bucketPtr->isEmpty()); const int oldExtent = bucketPtr->monsterBucketExtent(); deleteBucket(bucketNumber); //Delete the monster-bucket for (int index = bucketNumber; index < bucketNumber + 1 + oldExtent; ++index) { Q_ASSERT(!m_buckets[index]); m_buckets[index] = new MyBucket(); m_buckets[index]->initialize(0); Q_ASSERT(!m_buckets[index]->monsterBucketExtent()); } } return m_buckets[bucketNumber]; } MyBucket* bucketForIndex(short unsigned int index) const { MyBucket* bucketPtr = m_buckets.at(index); if (!bucketPtr) { initializeBucket(index); bucketPtr = m_buckets.at(index); } return bucketPtr; } bool open(const QString& path) override { QMutexLocker lock(m_mutex); close(); //qDebug() << "opening repository" << m_repositoryName << "at" << path; QDir dir(path); m_file = new QFile(dir.absoluteFilePath(m_repositoryName)); m_dynamicFile = new QFile(dir.absoluteFilePath(m_repositoryName + QLatin1String("_dynamic"))); if (!m_file->open(QFile::ReadWrite) || !m_dynamicFile->open(QFile::ReadWrite)) { delete m_file; m_file = nullptr; delete m_dynamicFile; m_dynamicFile = nullptr; return false; } m_metaDataChanged = true; if (m_file->size() == 0) { m_file->resize(0); m_file->write(( char* )&m_repositoryVersion, sizeof(uint)); uint hashSize = bucketHashSize; m_file->write(( char* )&hashSize, sizeof(uint)); uint itemRepositoryVersion = staticItemRepositoryVersion(); m_file->write(( char* )&itemRepositoryVersion, sizeof(uint)); m_statBucketHashClashes = m_statItemCount = 0; m_file->write(( char* )&m_statBucketHashClashes, sizeof(uint)); m_file->write(( char* )&m_statItemCount, sizeof(uint)); m_buckets.resize(10); m_buckets.fill(nullptr); uint bucketCount = m_buckets.size(); m_file->write(( char* )&bucketCount, sizeof(uint)); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); m_currentBucket = 1; //Skip the first bucket, we won't use it so we have the zero indices for special purposes m_file->write(( char* )&m_currentBucket, sizeof(uint)); m_file->write(( char* )m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); //We have completely initialized the file now if (m_file->pos() != BucketStartOffset) { KMessageBox::error(nullptr, i18n("Failed writing to %1, probably the disk is full", m_file->fileName())); abort(); } const uint freeSpaceBucketsSize = 0; m_dynamicFile->write(( char* )&freeSpaceBucketsSize, sizeof(uint)); m_freeSpaceBuckets.clear(); } else { m_file->close(); bool res = m_file->open(QFile::ReadOnly); //Re-open in read-only mode, so we create a read-only m_fileMap VERIFY(res); //Check that the version is correct uint storedVersion = 0, hashSize = 0, itemRepositoryVersion = 0; m_file->read(( char* )&storedVersion, sizeof(uint)); m_file->read(( char* )&hashSize, sizeof(uint)); m_file->read(( char* )&itemRepositoryVersion, sizeof(uint)); m_file->read(( char* )&m_statBucketHashClashes, sizeof(uint)); m_file->read(( char* )&m_statItemCount, sizeof(uint)); if (storedVersion != m_repositoryVersion || hashSize != bucketHashSize || itemRepositoryVersion != staticItemRepositoryVersion()) { qDebug() << "repository" << m_repositoryName << "version mismatch in" << m_file->fileName() << ", stored: version " << storedVersion << "hashsize" << hashSize << "repository-version" << itemRepositoryVersion << " current: version" << m_repositoryVersion << "hashsize" << bucketHashSize << "repository-version" << staticItemRepositoryVersion(); delete m_file; m_file = nullptr; delete m_dynamicFile; m_dynamicFile = nullptr; return false; } m_metaDataChanged = false; uint bucketCount = 0; m_file->read(( char* )&bucketCount, sizeof(uint)); m_buckets.resize(bucketCount); m_file->read(( char* )&m_currentBucket, sizeof(uint)); m_file->read(( char* )m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); Q_ASSERT(m_file->pos() == BucketStartOffset); uint freeSpaceBucketsSize = 0; m_dynamicFile->read(( char* )&freeSpaceBucketsSize, sizeof(uint)); m_freeSpaceBuckets.resize(freeSpaceBucketsSize); m_dynamicFile->read(( char* )m_freeSpaceBuckets.data(), sizeof(uint) * freeSpaceBucketsSize); } m_fileMapSize = 0; m_fileMap = nullptr; #ifdef ITEMREPOSITORY_USE_MMAP_LOADING if (m_file->size() > BucketStartOffset) { m_fileMap = m_file->map(BucketStartOffset, m_file->size() - BucketStartOffset); Q_ASSERT(m_file->isOpen()); Q_ASSERT(m_file->size() >= BucketStartOffset); if (m_fileMap) { m_fileMapSize = m_file->size() - BucketStartOffset; } else { qWarning() << "mapping" << m_file->fileName() << "FAILED!"; } } #endif //To protect us from inconsistency due to crashes. flush() is not enough. m_file->close(); m_dynamicFile->close(); return true; } ///@warning by default, this does not store the current state to disk. void close(bool doStore = false) override { if (doStore) store(); if (m_file) m_file->close(); delete m_file; m_file = nullptr; m_fileMap = nullptr; m_fileMapSize = 0; if (m_dynamicFile) m_dynamicFile->close(); delete m_dynamicFile; m_dynamicFile = nullptr; qDeleteAll(m_buckets); m_buckets.clear(); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); } struct AllItemsReachableVisitor { explicit AllItemsReachableVisitor(ItemRepository* rep) : repository(rep) { } bool operator()(const Item* item) { return repository->itemReachable(item); } ItemRepository* repository; }; //Returns whether the given item is reachable through its hash bool itemReachable(const Item* item) const { const uint hash = item->hash(); return walkBucketChain(hash, [ = ](ushort /*bucketIndex*/, const MyBucket* bucketPtr) { return bucketPtr->itemReachable(item, hash); }); } //Returns true if all items in the given bucket are reachable through their hashes bool allItemsReachable(unsigned short bucket) { if (!bucket) return true; MyBucket* bucketPtr = bucketForIndex(bucket); AllItemsReachableVisitor visitor(this); return bucketPtr->visitAllItems(visitor); } int finalCleanup() override { ThisLocker lock(m_mutex); int changed = 0; for (int a = 1; a <= m_currentBucket; ++a) { MyBucket* bucket = bucketForIndex(a); if (bucket && bucket->dirty()) { ///@todo Faster dirty check, without loading bucket changed += bucket->finalCleanup(*this); } a += bucket->monsterBucketExtent(); //Skip buckets that are attached as tail to monster-buckets } return changed; } inline void initializeBucket(int bucketNumber) const { Q_ASSERT(bucketNumber); #ifdef DEBUG_MONSTERBUCKETS for (uint offset = 1; offset < 5; ++offset) { int test = bucketNumber - offset; if (test >= 0 && m_buckets[test]) { Q_ASSERT(m_buckets[test]->monsterBucketExtent() < offset); } } #endif if (!m_buckets[bucketNumber]) { m_buckets[bucketNumber] = new MyBucket(); bool doMMapLoading = ( bool )m_fileMap; uint offset = ((bucketNumber - 1) * MyBucket::DataSize); if (m_file && offset < m_fileMapSize && doMMapLoading && *reinterpret_cast(m_fileMap + offset) == 0) { // qDebug() << "loading bucket mmap:" << bucketNumber; m_buckets[bucketNumber]->initializeFromMap(reinterpret_cast(m_fileMap + offset)); } else if (m_file) { //Either memory-mapping is disabled, or the item is not in the existing memory-map, //so we have to load it the classical way. bool res = m_file->open(QFile::ReadOnly); if (offset + BucketStartOffset < m_file->size()) { VERIFY(res); offset += BucketStartOffset; m_file->seek(offset); uint monsterBucketExtent; m_file->read(( char* )(&monsterBucketExtent), sizeof(unsigned int)); m_file->seek(offset); ///FIXME: use the data here instead of copying it again in prepareChange QByteArray data = m_file->read((1 + monsterBucketExtent) * MyBucket::DataSize); m_buckets[bucketNumber]->initializeFromMap(data.data()); m_buckets[bucketNumber]->prepareChange(); } else { m_buckets[bucketNumber]->initialize(0); } m_file->close(); } else { m_buckets[bucketNumber]->initialize(0); } } else { m_buckets[bucketNumber]->initialize(0); } } ///Can only be called on empty buckets void deleteBucket(int bucketNumber) { Q_ASSERT(bucketForIndex(bucketNumber)->isEmpty()); Q_ASSERT(bucketForIndex(bucketNumber)->noNextBuckets()); delete m_buckets[bucketNumber]; m_buckets[bucketNumber] = nullptr; } //m_file must be opened void storeBucket(int bucketNumber) const { if (m_file && m_buckets[bucketNumber]) { m_buckets[bucketNumber]->store(m_file, BucketStartOffset + (bucketNumber - 1) * MyBucket::DataSize); } } /// If mustFindBucket is zero, the whole chain is just walked. This is good for debugging for infinite recursion. /// @return whether @p mustFindBucket was found bool walkBucketLinks(uint checkBucket, uint hash, uint mustFindBucket = 0) const { bool found = false; while (checkBucket) { if (checkBucket == mustFindBucket) found = true; checkBucket = bucketForIndex(checkBucket)->nextBucketForHash(hash); } return found || (mustFindBucket == 0); } /// Computes the bucket where the chains opened by the buckets @p mainHead and @p intersectorHead /// with hash @p hash meet each other. /// @return QPair hashChainIntersection(uint mainHead, uint intersectorHead, uint hash) const { uint previous = 0; uint current = mainHead; while (current) { ///@todo Make this more efficient if (walkBucketLinks(intersectorHead, hash, current)) return qMakePair(previous, current); previous = current; current = bucketForIndex(current)->nextBucketForHash(hash); } return qMakePair(0u, 0u); } void putIntoFreeList(unsigned short bucket, MyBucket* bucketPtr) { Q_ASSERT(!bucketPtr->monsterBucketExtent()); int indexInFree = m_freeSpaceBuckets.indexOf(bucket); if (indexInFree == -1 && (bucketPtr->freeItemCount() >= MyBucket::MinFreeItemsForReuse || bucketPtr->largestFreeSize() >= MyBucket::MinFreeSizeForReuse)) { //Add the bucket to the list of buckets from where to re-assign free space //We only do it when a specific threshold of empty items is reached, because that way items can stay "somewhat" semantically ordered. Q_ASSERT(bucketPtr->largestFreeSize()); int insertPos; for (insertPos = 0; insertPos < m_freeSpaceBuckets.size(); ++insertPos) { if (bucketForIndex(m_freeSpaceBuckets[insertPos])->largestFreeSize() > bucketPtr->largestFreeSize()) break; } m_freeSpaceBuckets.insert(insertPos, bucket); updateFreeSpaceOrder(insertPos); } else if (indexInFree != -1) { ///Re-order so the order in m_freeSpaceBuckets is correct(sorted by largest free item size) updateFreeSpaceOrder(indexInFree); } #ifdef DEBUG_MONSTERBUCKETS if (bucketPtr->isEmpty()) { Q_ASSERT(m_freeSpaceBuckets.contains(bucket)); } #endif } void verifyIndex(uint index) const { // We don't use zero indices Q_ASSERT(index); int bucket = (index >> 16); // nor zero buckets Q_ASSERT(bucket); Q_ASSERT_X(bucket < m_buckets.size(), Q_FUNC_INFO, qPrintable(QStringLiteral("index %1 gives invalid bucket number %2, current count is: %3") .arg(index) .arg(bucket) .arg(m_buckets.size()))); // don't trigger compile warnings in release mode Q_UNUSED(bucket); Q_UNUSED(index); } bool m_metaDataChanged; mutable QMutex m_ownMutex; mutable QMutex* m_mutex; QString m_repositoryName; mutable int m_currentBucket; //List of buckets that have free space available that can be assigned. Sorted by size: Smallest space first. Second order sorting: Bucket index QVector m_freeSpaceBuckets; mutable QVector m_buckets; uint m_statBucketHashClashes, m_statItemCount; //Maps hash-values modulo 1< 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 ITEMREPOSITORYEXAMPLEITEM_H #define ITEMREPOSITORYEXAMPLEITEM_H #include "itemrepository.h" namespace KDevelop { /** * This is the actual data that is stored in the repository. All the data that is not directly in the class-body, * like the text of a string, can be stored behind the item in the same memory region. The only important thing is * that the Request item (@see ExampleItemRequest) correctly advertises the space needed by this item. */ class ExampleItem { /// @returns The item's hash. /// @warning The hash returned shall be exactly same as the return value of @ref ExampleItemRequest::hash() /// of the item request used to create this item. unsigned int hash() const { return 0; } //Every item has to implement this function, and return the complete size this item takes in memory. //Must be exactly the same value as ExampleItemRequest::itemSize() has returned while creating the item. /// @returns The item's size. /// @warning The size returned shall be exactly same as the return value @ref ExampleItemRequest::itemSize() /// of the item request used to created this item. unsigned short int itemSize() const { return 0; } }; /** * A request represents the information that is searched in the repository. * It must be able to compare itself to items stored in the repository, and it must be able to * create items in the. The item-types can also be basic data-types, with additional information stored behind. * It must have a static destroy() member, that does any action that needs to be done before the item is removed from * the repository again. */ class ExampleItemRequest { enum { AverageSize = 10 //! This should be the approximate average size of an Item requested. }; - typedef unsigned int HashType; + using HashType = unsigned int; /// @returns The hash associated with this request (e. g. the hash of a string). HashType hash() const { return 0; } /// @returns The size of an item created with @ref createItem(). uint itemSize() const { return 0; } /// Should create an item where the information of the requested item is permanently stored. /// @param item A pointer an allocated range with the size of @ref itemSize(). /// @warning Never call non-constant functions on the repository from within this function! void createItem(ExampleItem* item) const { Q_UNUSED(item); } static void destroy(ExampleItem* item, AbstractItemRepository&) { Q_UNUSED(item); } /// @returns Whether this item should be disk-persistent. static bool persistent(ExampleItem*) { return true; } /// @returns Whether the requested item equals the given one (@p item). bool equals(const ExampleItem* item) const { Q_UNUSED(item); return false; } }; } #endif // ITEMREPOSITORYEXAMPLEITEM_H diff --git a/kdevplatform/serialization/stringrepository.h b/kdevplatform/serialization/stringrepository.h index 33fe30c193..4863784e8e 100644 --- a/kdevplatform/serialization/stringrepository.h +++ b/kdevplatform/serialization/stringrepository.h @@ -1,120 +1,120 @@ /* 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. */ #ifndef KDEVPLATFORM_STRINGREPOSITORY_H #define KDEVPLATFORM_STRINGREPOSITORY_H #include #include "itemrepository.h" #include "indexedstring.h" namespace Repositories { using namespace KDevelop; struct StringData { unsigned short length; unsigned int itemSize() const { return sizeof(StringData) + length; } unsigned int hash() const { IndexedString::RunningHash running; const char* str = (( const char* )this) + sizeof(StringData); for (int a = length - 1; a >= 0; --a) { running.append(*str); ++str; } return running.hash; } }; struct StringRepositoryItemRequest { //The text is supposed to be utf8 encoded StringRepositoryItemRequest(const char* text, unsigned int hash, unsigned short length) : m_hash(hash) , m_length(length) , m_text(text) { } enum { AverageSize = 10 //This should be the approximate average size of an Item }; - typedef unsigned int HashType; + using HashType = unsigned int; //Should return the hash-value associated with this request(For example the hash of a string) HashType hash() const { return m_hash; } //Should return the size of an item created with createItem uint itemSize() const { return sizeof(StringData) + m_length; } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(StringData* item) const { item->length = m_length; ++item; memcpy(item, m_text, m_length); } static void destroy(StringData*, KDevelop::AbstractItemRepository&) { } static bool persistent(const StringData*) { //Reference-counting not supported in the normal string repository return true; } //Should return whether the here requested item equals the given item bool equals(const StringData* item) const { return item->length == m_length && (memcmp(++item, m_text, m_length) == 0); } unsigned int m_hash; unsigned short m_length; const char* m_text; }; -typedef ItemRepository StringRepository; +using StringRepository = ItemRepository; ///@param item must be valid(nonzero) inline QString stringFromItem(const StringData* item) { const unsigned short* textPos = ( unsigned short* )(item + 1); return QString::fromUtf8(( char* )textPos, item->length); } inline QByteArray arrayFromItem(const StringData* item) { const unsigned short* textPos = ( unsigned short* )(item + 1); return QByteArray(( char* )textPos, item->length); } } #endif diff --git a/kdevplatform/serialization/tests/bench_itemrepository.cpp b/kdevplatform/serialization/tests/bench_itemrepository.cpp index b69a36562d..2ee4e0ef8c 100644 --- a/kdevplatform/serialization/tests/bench_itemrepository.cpp +++ b/kdevplatform/serialization/tests/bench_itemrepository.cpp @@ -1,223 +1,223 @@ /* * This file is part of KDevelop * Copyright 2012-2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "bench_itemrepository.h" #include #include #include #include #include QTEST_GUILESS_MAIN(BenchItemRepository) using namespace KDevelop; struct TestData { uint length; uint itemSize() const { return sizeof(TestData) + length; } uint hash() const { const char* str = (( const char* )this) + sizeof(TestData); return IndexedString::hashString(str, length); } }; struct TestDataRepositoryItemRequest { //The text is supposed to be utf8 encoded TestDataRepositoryItemRequest(const char* text, uint length) : m_length(length) , m_text(text) , m_hash(IndexedString::hashString(text, length)) { } enum { AverageSize = 10 //This should be the approximate average size of an Item }; - typedef uint HashType; + using HashType = uint; //Should return the hash-value associated with this request(For example the hash of a string) HashType hash() const { return m_hash; } //Should return the size of an item created with createItem uint itemSize() const { return sizeof(TestData) + m_length; } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(TestData* item) const { item->length = m_length; ++item; memcpy(item, m_text, m_length); } static void destroy(TestData* item, AbstractItemRepository&) { Q_UNUSED(item); //Nothing to do here (The object is not intelligent) } static bool persistent(const TestData* item) { Q_UNUSED(item); return true; } //Should return whether the here requested item equals the given item bool equals(const TestData* item) const { return item->length == m_length && (memcmp(++item, m_text, m_length) == 0); } unsigned short m_length; const char* m_text; unsigned int m_hash; }; -typedef ItemRepository TestDataRepository; +using TestDataRepository = ItemRepository; void BenchItemRepository::initTestCase() { ItemRepositoryRegistry::initialize(m_repositoryPath); } void BenchItemRepository::cleanupTestCase() { ItemRepositoryRegistry::deleteRepositoryFromDisk(m_repositoryPath); } static QVector generateData() { QVector data; static const int NUM_ITEMS = 100000; data.resize(NUM_ITEMS); for (int i = 0; i < NUM_ITEMS; ++i) { data[i] = QStringLiteral("/foo/%1").arg(i); } return data; } static QVector insertData(const QVector& data, TestDataRepository& repo) { QVector indices; indices.reserve(data.size()); for (const QString& item : data) { const QByteArray byteArray = item.toUtf8(); indices << repo.index(TestDataRepositoryItemRequest(byteArray.constData(), byteArray.length())); } return indices; } void BenchItemRepository::insert() { TestDataRepository repo("TestDataRepositoryInsert"); const QVector data = generateData(); QVector indices; QBENCHMARK_ONCE { indices = insertData(data, repo); repo.store(); } Q_ASSERT(indices.size() == data.size()); QCOMPARE(repo.statistics().totalItems, uint(data.size())); } void BenchItemRepository::remove() { TestDataRepository repo("TestDataRepositoryRemove"); const QVector data = generateData(); const QVector indices = insertData(data, repo); repo.store(); QVERIFY(indices.size() == indices.toList().toSet().size()); QVERIFY(indices.size() == data.size()); QBENCHMARK_ONCE { for (uint index : indices) { repo.deleteItem(index); } repo.store(); } QCOMPARE(repo.statistics().totalItems, 0u); } void BenchItemRepository::removeDisk() { const QVector data = generateData(); QVector indices; { TestDataRepository repo("TestDataRepositoryRemoveDisk"); indices = insertData(data, repo); repo.store(); } TestDataRepository repo("TestDataRepositoryRemoveDisk"); QVERIFY(repo.statistics().totalItems == static_cast(data.size())); QBENCHMARK_ONCE { for (uint index : qAsConst(indices)) { repo.deleteItem(index); } repo.store(); } QCOMPARE(repo.statistics().totalItems, 0u); } void BenchItemRepository::lookupKey() { TestDataRepository repo("TestDataRepositoryLookupKey"); const QVector data = generateData(); QVector indices = insertData(data, repo); srand(0); std::random_shuffle(indices.begin(), indices.end()); QBENCHMARK { for (uint index : qAsConst(indices)) { repo.itemFromIndex(index); } } } void BenchItemRepository::lookupValue() { TestDataRepository repo("TestDataRepositoryLookupValue"); const QVector data = generateData(); QVector indices = insertData(data, repo); srand(0); std::random_shuffle(indices.begin(), indices.end()); QBENCHMARK { for (const QString& item : data) { const QByteArray byteArray = item.toUtf8(); repo.findIndex(TestDataRepositoryItemRequest(byteArray.constData(), byteArray.length())); } } } diff --git a/kdevplatform/shell/languagecontroller.cpp b/kdevplatform/shell/languagecontroller.cpp index 0f292595db..1e62e98280 100644 --- a/kdevplatform/shell/languagecontroller.cpp +++ b/kdevplatform/shell/languagecontroller.cpp @@ -1,382 +1,382 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * 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. * ***************************************************************************/ #include "languagecontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemmodelset.h" #include "core.h" #include "settings/languagepreferences.h" #include "completionsettings.h" #include "debug.h" namespace { QString KEY_SupportedMimeTypes() { return QStringLiteral("X-KDevelop-SupportedMimeTypes"); } QString KEY_ILanguageSupport() { return QStringLiteral("ILanguageSupport"); } } #if QT_VERSION < 0x050600 inline uint qHash(const QMimeType& mime, uint seed = 0) { return qHash(mime.name(), seed); } #endif namespace KDevelop { -typedef QHash LanguageHash; -typedef QHash > LanguageCache; +using LanguageHash = QHash; +using LanguageCache = QHash>; class LanguageControllerPrivate { public: explicit LanguageControllerPrivate(LanguageController *controller) : dataMutex(QMutex::Recursive) , backgroundParser(new BackgroundParser(controller)) , staticAssistantsManager(nullptr) , m_cleanedUp(false) , problemModelSet(new ProblemModelSet(controller)) , m_controller(controller) {} void documentActivated(KDevelop::IDocument *document) { QUrl url = document->url(); if (!url.isValid()) { return; } activeLanguages.clear(); const QList languages = m_controller->languagesForUrl(url); activeLanguages.reserve(languages.size()); for (const auto lang : languages) { activeLanguages << lang; } } QList activeLanguages; mutable QMutex dataMutex; LanguageHash languages; //Maps language-names to languages LanguageCache languageCache; //Maps mimetype-names to languages - typedef QMultiHash MimeTypeCache; + using MimeTypeCache = QMultiHash; MimeTypeCache mimeTypeCache; //Maps mimetypes to languages BackgroundParser* const backgroundParser; StaticAssistantsManager* staticAssistantsManager; bool m_cleanedUp; void addLanguageSupport(ILanguageSupport* support, const QStringList& mimetypes); void addLanguageSupport(ILanguageSupport* support); ProblemModelSet* const problemModelSet; private: LanguageController* const m_controller; }; void LanguageControllerPrivate::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { Q_ASSERT(!languages.contains(languageSupport->name())); languages.insert(languageSupport->name(), languageSupport); for (const QString& mimeTypeName : mimetypes) { qCDebug(SHELL) << "adding supported mimetype:" << mimeTypeName << "language:" << languageSupport->name(); languageCache[mimeTypeName] << languageSupport; QMimeType mime = QMimeDatabase().mimeTypeForName(mimeTypeName); if (mime.isValid()) { mimeTypeCache.insert(mime, languageSupport); } else { qCWarning(SHELL) << "could not create mime-type" << mimeTypeName; } } } void LanguageControllerPrivate::addLanguageSupport(KDevelop::ILanguageSupport* languageSupport) { if (languages.contains(languageSupport->name())) return; Q_ASSERT(dynamic_cast(languageSupport)); KPluginMetaData info = Core::self()->pluginController()->pluginInfo(dynamic_cast(languageSupport)); QStringList mimetypes = KPluginMetaData::readStringList(info.rawData(), KEY_SupportedMimeTypes()); addLanguageSupport(languageSupport, mimetypes); } LanguageController::LanguageController(QObject *parent) : ILanguageController(parent) , d(new LanguageControllerPrivate(this)) { setObjectName(QStringLiteral("LanguageController")); } LanguageController::~LanguageController() = default; void LanguageController::initialize() { d->backgroundParser->loadSettings(); d->staticAssistantsManager = new StaticAssistantsManager(this); // make sure the DUChain is setup before we try to access it from different threads at the same time DUChain::self(); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, [&] (IDocument* document) { d->documentActivated(document); }); } void LanguageController::cleanup() { QMutexLocker lock(&d->dataMutex); d->m_cleanedUp = true; } QList LanguageController::activeLanguages() { QMutexLocker lock(&d->dataMutex); return d->activeLanguages; } StaticAssistantsManager* LanguageController::staticAssistantsManager() const { return d->staticAssistantsManager; } ICompletionSettings *LanguageController::completionSettings() const { return &CompletionSettings::self(); } ProblemModelSet* LanguageController::problemModelSet() const { return d->problemModelSet; } QList LanguageController::loadedLanguages() const { QMutexLocker lock(&d->dataMutex); QList ret; if(d->m_cleanedUp) return ret; ret.reserve(d->languages.size()); foreach(ILanguageSupport* lang, d->languages) ret << lang; return ret; } ILanguageSupport* LanguageController::language(const QString &name) const { QMutexLocker lock(&d->dataMutex); if(d->m_cleanedUp) return nullptr; if(d->languages.contains(name)) return d->languages[name]; // temporary support for deprecated-in-5.1 "X-KDevelop-Language" as fallback // remove in later version const QString keys[2] = { QStringLiteral("X-KDevelop-Languages"), QStringLiteral("X-KDevelop-Language") }; QList supports; for (const auto& key : keys) { QVariantMap constraints; constraints.insert(key, name); supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport(), constraints); if (key == keys[1]) { for (auto support : qAsConst(supports)) { qCWarning(SHELL) << "Plugin" << Core::self()->pluginController()->pluginInfo(support).name() << " has deprecated (since 5.1) metadata key \"X-KDevelop-Language\", needs porting to: \"X-KDevelop-Languages\": ["<extension(); if(languageSupport) { d->addLanguageSupport(languageSupport); return languageSupport; } } return nullptr; } bool isNumeric(const QString& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str[a].isNumber()) return false; return true; } QList LanguageController::languagesForUrl(const QUrl &url) { QMutexLocker lock(&d->dataMutex); QList languages; if(d->m_cleanedUp) return languages; const QString fileName = url.fileName(); ///TODO: cache regexp or simple string pattern for endsWith matching QRegExp exp(QString(), Qt::CaseInsensitive, QRegExp::Wildcard); ///non-crashy part: Use the mime-types of known languages for(LanguageControllerPrivate::MimeTypeCache::const_iterator it = d->mimeTypeCache.constBegin(); it != d->mimeTypeCache.constEnd(); ++it) { foreach(const QString& pattern, it.key().globPatterns()) { if(pattern.startsWith(QLatin1Char('*'))) { const QStringRef subPattern = pattern.midRef(1); if (!subPattern.contains(QLatin1Char('*'))) { //optimize: we can skip the expensive QRegExp in this case //and do a simple string compare (much faster) if (fileName.endsWith(subPattern)) { languages << *it; } continue; } } exp.setPattern(pattern); if(int position = exp.indexIn(fileName)) { if(position != -1 && exp.matchedLength() + position == fileName.length()) languages << *it; } } } if(!languages.isEmpty()) return languages; //Never use findByUrl from within a background thread, and never load a language support //from within the backgruond thread. Both is unsafe, and can lead to crashes if(!languages.isEmpty() || QThread::currentThread() != thread()) return languages; QMimeType mimeType; if (url.isLocalFile()) { mimeType = QMimeDatabase().mimeTypeForFile(url.toLocalFile()); } else { // remote file, only look at the extension mimeType = QMimeDatabase().mimeTypeForUrl(url); } if (mimeType.isDefault()) { // ask the document controller about a more concrete mimetype IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { mimeType = doc->mimeType(); } } languages = languagesForMimetype(mimeType.name()); return languages; } QList LanguageController::languagesForMimetype(const QString& mimetype) { QMutexLocker lock(&d->dataMutex); QList languages; LanguageCache::ConstIterator it = d->languageCache.constFind(mimetype); if (it != d->languageCache.constEnd()) { languages = it.value(); } else { QVariantMap constraints; constraints.insert(KEY_SupportedMimeTypes(), mimetype); const QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport(), constraints); if (supports.isEmpty()) { qCDebug(SHELL) << "no languages for mimetype:" << mimetype; d->languageCache.insert(mimetype, QList()); } else { for (IPlugin *support : supports) { auto* languageSupport = support->extension(); qCDebug(SHELL) << "language-support:" << languageSupport; if(languageSupport) { d->addLanguageSupport(languageSupport); languages << languageSupport; } } } } return languages; } QList LanguageController::mimetypesForLanguageName(const QString& languageName) { QMutexLocker lock(&d->dataMutex); QList mimetypes; for (LanguageCache::ConstIterator iter = d->languageCache.constBegin(); iter != d->languageCache.constEnd(); ++iter) { foreach (ILanguageSupport* language, iter.value()) { if (language->name() == languageName) { mimetypes << iter.key(); break; } } } return mimetypes; } BackgroundParser *LanguageController::backgroundParser() const { return d->backgroundParser; } void LanguageController::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { d->addLanguageSupport(languageSupport, mimetypes); } } #include "moc_languagecontroller.cpp" diff --git a/kdevplatform/shell/plugincontroller.cpp b/kdevplatform/shell/plugincontroller.cpp index 58f523cc90..efa7f44b59 100644 --- a/kdevplatform/shell/plugincontroller.cpp +++ b/kdevplatform/shell/plugincontroller.cpp @@ -1,820 +1,820 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright (c) 2002-2003 Martijn Klingens 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 "plugincontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "shellextension.h" #include "runcontroller.h" #include "debugcontroller.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "projectcontroller.h" #include "ktexteditorpluginintegration.h" #include "debug.h" namespace { inline QString KEY_Plugins() { return QStringLiteral("Plugins"); } inline QString KEY_Suffix_Enabled() { return QStringLiteral("Enabled"); } inline QString KEY_LoadMode() { return QStringLiteral("X-KDevelop-LoadMode"); } inline QString KEY_Category() { return QStringLiteral("X-KDevelop-Category"); } inline QString KEY_Mode() { return QStringLiteral("X-KDevelop-Mode"); } inline QString KEY_Version() { return QStringLiteral("X-KDevelop-Version"); } inline QString KEY_Interfaces() { return QStringLiteral("X-KDevelop-Interfaces"); } inline QString KEY_Required() { return QStringLiteral("X-KDevelop-IRequired"); } inline QString KEY_Optional() { return QStringLiteral("X-KDevelop-IOptional"); } inline QString KEY_KPlugin() { return QStringLiteral("KPlugin"); } inline QString KEY_EnabledByDefault() { return QStringLiteral("EnabledByDefault"); } inline QString KEY_Global() { return QStringLiteral("Global"); } inline QString KEY_Project() { return QStringLiteral("Project"); } inline QString KEY_Gui() { return QStringLiteral("GUI"); } inline QString KEY_AlwaysOn() { return QStringLiteral("AlwaysOn"); } inline QString KEY_UserSelectable() { return QStringLiteral("UserSelectable"); } bool isUserSelectable( const KPluginMetaData& info ) { QString loadMode = info.value(KEY_LoadMode()); return loadMode.isEmpty() || loadMode == KEY_UserSelectable(); } bool isGlobalPlugin( const KPluginMetaData& info ) { return info.value(KEY_Category()) == KEY_Global(); } bool hasMandatoryProperties( const KPluginMetaData& info ) { QString mode = info.value(KEY_Mode()); if (mode.isEmpty()) { return false; } // when the plugin is installed into the versioned plugin path, it's good to go if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) { return true; } // the version property is only required when the plugin is not installed into the right directory QVariant version = info.rawData().value(KEY_Version()).toVariant(); if (version.isValid() && version.value() == KDEVELOP_PLUGIN_VERSION) { return true; } return false; } bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints) { for (auto it = constraints.begin(); it != constraints.end(); ++it) { const auto property = info.rawData().value(it.key()).toVariant(); if (!property.isValid()) { return false; } else if (property.canConvert()) { QSet values = property.toStringList().toSet(); QSet expected = it.value().toStringList().toSet(); if (!values.contains(expected)) { return false; } } else if (it.value() != property) { return false; } } return true; } struct Dependency { explicit Dependency(const QString &dependency) : interface(dependency) { if (dependency.contains(QLatin1Char('@'))) { const auto list = dependency.split(QLatin1Char('@'), QString::SkipEmptyParts); if (list.size() == 2) { interface = list.at(0); pluginName = list.at(1); } } } QString interface; QString pluginName; }; } namespace KDevelop { class PluginControllerPrivate { public: explicit PluginControllerPrivate(Core *core) : core(core) {} QVector plugins; //map plugin infos to currently loaded plugins - typedef QHash InfoToPluginMap; + using InfoToPluginMap = QHash; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during shutdown by the // async unloading of plugins. enum CleanupMode { Running /**< the plugin manager is running */, CleaningUp /**< the plugin manager is cleaning up for shutdown */, CleanupDone /**< the plugin manager has finished cleaning up */ }; CleanupMode cleanupMode; bool canUnload(const KPluginMetaData& plugin) { qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode()); if (plugin.value(KEY_LoadMode()) == KEY_AlwaysOn()) { return false; } const QStringList interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces()); qCDebug(SHELL) << "checking dependencies:" << interfaces; foreach (const KPluginMetaData& info, loadedPlugins.keys()) { if (info.pluginId() != plugin.pluginId()) { const QStringList dependencies = KPluginMetaData::readStringList(plugin.rawData(), KEY_Required()) + KPluginMetaData::readStringList(plugin.rawData(), KEY_Optional()); for (const QString& dep : dependencies) { Dependency dependency(dep); if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) { continue; } if (interfaces.contains(dependency.interface) && !canUnload(info)) { return false; } } } } return true; } KPluginMetaData infoForId( const QString& id ) const { for (const KPluginMetaData& info : plugins) { if (info.pluginId() == id) { return info; } } return KPluginMetaData(); } /** * Iterate over all cached plugin infos, and call the functor for every enabled plugin. * * If an extension and/or pluginName is given, the functor will only be called for * those plugins matching this information. * * The functor should return false when the iteration can be stopped, and true if it * should be continued. */ template void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) { foreach (const auto& info, plugins) { if ((pluginName.isEmpty() || info.pluginId() == pluginName) && (extension.isEmpty() || KPluginMetaData::readStringList(info.rawData(), KEY_Interfaces()).contains(extension)) && constraintsMatch(info, constraints) && isEnabled(info)) { if (!func(info)) { break; } } } } enum EnableState { DisabledByEnv, DisabledBySetting, DisabledByUnknown, FirstEnabledState, EnabledBySetting = FirstEnabledState, AlwaysEnabled }; /** * Estimate enabled state of a plugin */ EnableState enabledState(const KPluginMetaData& info) const { // first check black listing from environment static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(QLatin1Char(';')); if (disabledPlugins.contains(info.pluginId())) { return DisabledByEnv; } if (!isUserSelectable( info )) return AlwaysEnabled; // read stored user preference const KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); const QString pluginEnabledKey = info.pluginId() + KEY_Suffix_Enabled(); if (grp.hasKey(pluginEnabledKey)) { return grp.readEntry(pluginEnabledKey, true) ? EnabledBySetting : DisabledBySetting; } // should not happen return DisabledByUnknown; } /** * Decide whether a plugin is enabled */ bool isEnabled(const KPluginMetaData& info) const { return (enabledState(info) >= FirstEnabledState); } Core* const core; }; PluginController::PluginController(Core *core) : IPluginController() , d(new PluginControllerPrivate(core)) { setObjectName(QStringLiteral("PluginController")); QSet foundPlugins; auto newPlugins = KPluginLoader::findPlugins(QStringLiteral("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION)), [&](const KPluginMetaData& meta) { if (meta.serviceTypes().contains(QStringLiteral("KDevelop/Plugin"))) { foundPlugins.insert(meta.pluginId()); return true; } else { qCWarning(SHELL) << "Plugin" << meta.fileName() << "is installed into the kdevplatform plugin directory, but does not have" " \"KDevelop/Plugin\" set as the service type. This plugin will not be loaded."; return false; } }); qCDebug(SHELL) << "Found" << newPlugins.size() << "plugins:" << foundPlugins; if (newPlugins.isEmpty()) { qCWarning(SHELL) << "Did not find any plugins, check your environment."; qCWarning(SHELL) << " Note: QT_PLUGIN_PATH is set to:" << qgetenv("QT_PLUGIN_PATH"); } d->plugins = newPlugins; KTextEditorIntegration::initialize(); const QVector ktePlugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData & md) { return md.serviceTypes().contains(QStringLiteral("KTextEditor/Plugin")) && md.serviceTypes().contains(QStringLiteral("KDevelop/Plugin")); }); foundPlugins.clear(); std::for_each(ktePlugins.cbegin(), ktePlugins.cend(), [&foundPlugins](const KPluginMetaData& data) { foundPlugins << data.pluginId(); }); qCDebug(SHELL) << "Found" << ktePlugins.size() << " KTextEditor plugins:" << foundPlugins; d->plugins.reserve(d->plugins.size() + ktePlugins.size()); for (const auto& info : ktePlugins) { auto data = info.rawData(); // add some KDevelop specific JSON data data[KEY_Category()] = KEY_Global(); data[KEY_Mode()] = KEY_Gui(); data[KEY_Version()] = KDEVELOP_PLUGIN_VERSION; d->plugins.append({data, info.fileName(), info.metaDataFileName()}); } d->cleanupMode = PluginControllerPrivate::Running; // Register the KDevelop::IPlugin* metatype so we can properly unload it qRegisterMetaType( "KDevelop::IPlugin*" ); } PluginController::~PluginController() { if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) { qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!"; } } KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const { return d->loadedPlugins.key(const_cast(plugin)); } void PluginController::cleanup() { if(d->cleanupMode != PluginControllerPrivate::Running) { //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode; return; } d->cleanupMode = PluginControllerPrivate::CleaningUp; // Ask all plugins to unload while ( !d->loadedPlugins.isEmpty() ) { //Let the plugin do some stuff before unloading unloadPlugin(d->loadedPlugins.begin().value(), Now); } d->cleanupMode = PluginControllerPrivate::CleanupDone; } IPlugin* PluginController::loadPlugin( const QString& pluginName ) { return loadPluginInternal( pluginName ); } void PluginController::initialize() { QElapsedTimer timer; timer.start(); QMap pluginMap; if( ShellExtension::getInstance()->defaultPlugins().isEmpty() ) { foreach( const KPluginMetaData& pi, d->plugins ) { QJsonValue enabledByDefaultValue = pi.rawData()[KEY_KPlugin()].toObject()[KEY_EnabledByDefault()]; // plugins enabled until explicitly specified otherwise const bool enabledByDefault = (enabledByDefaultValue.isNull() || enabledByDefaultValue.toBool()); pluginMap.insert(pi.pluginId(), enabledByDefault); } } else { // Get the default from the ShellExtension foreach( const QString& s, ShellExtension::getInstance()->defaultPlugins() ) { pluginMap.insert( s, true ); } } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); QMap entries = grp.entryMap(); QMap::Iterator it; for ( it = entries.begin(); it != entries.end(); ++it ) { const QString key = it.key(); if (key.endsWith(KEY_Suffix_Enabled())) { const QString pluginid = key.left(key.length() - 7); const bool defValue = pluginMap.value( pluginid, false ); const bool enabled = grp.readEntry(key, defValue); pluginMap.insert( pluginid, enabled ); } } // store current known set of enabled plugins foreach (const KPluginMetaData& pi, d->plugins) { if (isUserSelectable(pi)) { auto it = pluginMap.constFind(pi.pluginId()); if (it != pluginMap.constEnd() && (it.value())) { grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled(), true); } } else { // Backward compat: Remove any now-obsolete entries grp.deleteEntry(pi.pluginId() + QLatin1String("Disabled")); } } // Synchronize so we're writing out to the file. grp.sync(); // load global plugins foreach (const KPluginMetaData& pi, d->plugins) { if (isGlobalPlugin(pi)) { loadPluginInternal(pi.pluginId()); } } qCDebug(SHELL) << "Done loading plugins - took:" << timer.elapsed() << "ms"; } QList PluginController::loadedPlugins() const { return d->loadedPlugins.values(); } bool PluginController::unloadPlugin( const QString & pluginId ) { IPlugin *thePlugin = plugin( pluginId ); bool canUnload = d->canUnload( d->infoForId( pluginId ) ); qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload; if( thePlugin && canUnload ) { return unloadPlugin(thePlugin, Later); } return (canUnload && thePlugin); } bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion) { qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name(); emit unloadingPlugin(plugin); plugin->unload(); emit pluginUnloaded(plugin); //Remove the plugin from our list of plugins so we create a new //instance when we're asked for it again. //This is important to do right here, not later when the plugin really //vanishes. For example project re-opening might try to reload the plugin //and then would get the "old" pointer which will be deleted in the next //event loop run and thus causing crashes. for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) { if ( it.value() == plugin ) { d->loadedPlugins.erase( it ); break; } } if (deletion == Later) plugin->deleteLater(); else delete plugin; return true; } KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const { foreach (const KPluginMetaData& info, d->plugins) { if (info.pluginId() == pluginId) { return info; } } return KPluginMetaData(); } IPlugin *PluginController::loadPluginInternal( const QString &pluginId ) { QElapsedTimer timer; timer.start(); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) { qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ; return nullptr; } if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) { return plugin; } const auto enabledState = d->enabledState(info); if (enabledState < PluginControllerPrivate::FirstEnabledState) { // Do not load disabled plugins qCDebug(SHELL) << "Not loading plugin named" << pluginId << ( (enabledState == PluginControllerPrivate::DisabledByEnv) ? "because disabled by KDEV_DISABLE_PLUGINS." : (enabledState == PluginControllerPrivate::DisabledBySetting) ? "because disabled by setting." : /* else, should not happen */ "because disabled for unknown reason."); return nullptr; } if ( !hasMandatoryProperties( info ) ) { qCWarning(SHELL) << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set."; return nullptr; } if ( info.value(KEY_Mode()) == KEY_Gui() && Core::self()->setupFlags() == Core::NoUi ) { qCDebug(SHELL) << "Not loading plugin named" << pluginId << "- Running in No-Ui mode, but the plugin says it needs a GUI"; return nullptr; } qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name(); emit loadingPlugin( info.pluginId() ); // first, ensure all dependencies are available and not disabled // this is unrelated to whether they are loaded already or not. // when we depend on e.g. A and B, but B cannot be found, then we // do not want to load A first and then fail on B and leave A loaded. // this would happen if we'd skip this step here and directly loadDependencies. QStringList missingInterfaces; if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "some of its required dependencies could not be fulfilled:" << missingInterfaces.join(QLatin1Char(',')); return nullptr; } // now ensure all dependencies are loaded QString failedDependency; if( !loadDependencies( info, failedDependency ) ) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "because a required dependency could not be loaded:" << failedDependency; return nullptr; } // same for optional dependencies, but don't error out if anything fails loadOptionalDependencies( info ); // now we can finally load the plugin itself KPluginLoader loader(info.fileName()); auto factory = loader.factory(); if (!factory) { qCWarning(SHELL) << "Can't load plugin" << pluginId << "because a factory to load the plugin could not be obtained:" << loader.errorString(); return nullptr; } // now create it auto plugin = factory->create(d->core); if (!plugin) { if (auto katePlugin = factory->create(d->core, QVariantList() << info.pluginId())) { plugin = new KTextEditorIntegration::Plugin(katePlugin, d->core); } else { qCWarning(SHELL) << "Creating plugin" << pluginId << "failed."; return nullptr; } } KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins()); // runtime errors such as missing executables on the system or such get checked now if (plugin->hasError()) { qCWarning(SHELL) << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription() << "Disabling the plugin now."; group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), false); // do the same as KPluginInfo did group.sync(); unloadPlugin(pluginId); return nullptr; } // yay, it all worked - the plugin is loaded d->loadedPlugins.insert(info, plugin); group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), true); // do the same as KPluginInfo did group.sync(); qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << loader.fileName() << "- took:" << timer.elapsed() << "ms"; emit pluginLoaded( plugin ); return plugin; } IPlugin* PluginController::plugin( const QString& pluginId ) { KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) return nullptr; return d->loadedPlugins.value( info ); } bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const { QSet required = KPluginMetaData::readStringList(info.rawData(), KEY_Required()).toSet(); if (!required.isEmpty()) { d->foreachEnabledPlugin([&required] (const KPluginMetaData& plugin) -> bool { foreach (const QString& iface, KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces())) { required.remove(iface); required.remove(iface + QLatin1Char('@') + plugin.pluginId()); } return !required.isEmpty(); }); } // if we found all dependencies required should be empty now if (!required.isEmpty()) { missing = required.toList(); return false; } return true; } void PluginController::loadOptionalDependencies( const KPluginMetaData& info ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional()); for (const QString& dep : dependencies) { Dependency dependency(dep); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId(); } } } bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Required()); for (const QString& value : dependencies) { Dependency dependency(value); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { failedDependency = value; return false; } } return true; } IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints) { IPlugin* plugin = nullptr; d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool { plugin = d->loadedPlugins.value( info ); if( !plugin ) { plugin = loadPluginInternal( info.pluginId() ); } return !plugin; }, extension, constraints, pluginName); return plugin; } QList PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints) { //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints; QList plugins; d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool { IPlugin* plugin = d->loadedPlugins.value( info ); if( !plugin) { plugin = loadPluginInternal( info.pluginId() ); } if (plugin && !plugins.contains(plugin)) { plugins << plugin; } return true; }, extension, constraints); return plugins; } QVector PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { QVector plugins; d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool { plugins << info; return true; }, extension, constraints); return plugins; } QStringList PluginController::allPluginNames() { QStringList names; names.reserve(d->plugins.size()); Q_FOREACH( const KPluginMetaData& info , d->plugins ) { names << info.pluginId(); } return names; } QList PluginController::queryPluginsForContextMenuExtensions(KDevelop::Context* context, QWidget* parent) const { // This fixes random order of extension menu items between different runs of KDevelop. // Without sorting we have random reordering of "Analyze With" submenu for example: // 1) "Cppcheck" actions, "Vera++" actions - first run // 2) "Vera++" actions, "Cppcheck" actions - some other run. QMultiMap sortedPlugins; for (auto it = d->loadedPlugins.constBegin(); it != d->loadedPlugins.constEnd(); ++it) { sortedPlugins.insert(it.key().name(), it.value()); } QList exts; exts.reserve(sortedPlugins.size()); foreach (IPlugin* plugin, sortedPlugins) { exts << plugin->contextMenuExtension(context, parent); } exts << Core::self()->debugControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->documentationControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->runControllerInternal()->contextMenuExtension(context, parent); exts << Core::self()->projectControllerInternal()->contextMenuExtension(context, parent); return exts; } QStringList PluginController::projectPlugins() { QStringList names; foreach (const KPluginMetaData& info, d->plugins) { if (info.value(KEY_Category()) == KEY_Project()) { names << info.pluginId(); } } return names; } void PluginController::loadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { loadPluginInternal( name ); } } void PluginController::unloadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { unloadPlugin( name ); } } QVector PluginController::allPluginInfos() const { return d->plugins; } void PluginController::updateLoadedPlugins() { QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() ); foreach( const KPluginMetaData& info, d->plugins ) { if( isGlobalPlugin( info ) ) { bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled(), ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info ); bool loaded = d->loadedPlugins.contains( info ); if( loaded && !enabled ) { qCDebug(SHELL) << "unloading" << info.pluginId(); if( !unloadPlugin( info.pluginId() ) ) { grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled(), false ); } } else if( !loaded && enabled ) { loadPluginInternal( info.pluginId() ); } } // TODO: what about project plugins? what about dependency plugins? } } void PluginController::resetToDefaults() { KSharedConfigPtr cfg = Core::self()->activeSession()->config(); cfg->deleteGroup( KEY_Plugins() ); cfg->sync(); KConfigGroup grp = cfg->group( KEY_Plugins() ); QStringList plugins = ShellExtension::getInstance()->defaultPlugins(); if( plugins.isEmpty() ) { foreach( const KPluginMetaData& info, d->plugins ) { if (!isUserSelectable(info)) { continue; } QJsonValue enabledByDefault = info.rawData()[KEY_KPlugin()].toObject()[KEY_EnabledByDefault()]; // plugins enabled until explicitly specified otherwise if (enabledByDefault.isNull() || enabledByDefault.toBool()) { plugins << info.pluginId(); } } } foreach( const QString& s, plugins ) { grp.writeEntry(s + KEY_Suffix_Enabled(), true); } grp.sync(); } } diff --git a/kdevplatform/shell/progresswidget/progressmanager.h b/kdevplatform/shell/progresswidget/progressmanager.h index 31216dd7e9..ea395d0c47 100644 --- a/kdevplatform/shell/progresswidget/progressmanager.h +++ b/kdevplatform/shell/progresswidget/progressmanager.h @@ -1,477 +1,477 @@ /*************************************************************************** * (C) 2004 Till Adam * * * * 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 KDEVPLATFORM_PROGRESSMANAGER_H #define KDEVPLATFORM_PROGRESSMANAGER_H #include #include #include #include #include namespace Akonadi { class AgentInstance; } namespace KDevelop { class ProgressItem; class ProgressManager; -typedef QMap ProgressItemMap; +using ProgressItemMap = QMap; class ProgressItem : public QObject { Q_OBJECT friend class ProgressManager; public: /** * @return The id string which uniquely identifies the operation * represented by this item. */ const QString &id() const { return mId; } /** * @return The parent item of this one, if there is one. */ ProgressItem *parent() const { return mParent.data(); } /** * @return The user visible string to be used to represent this item. */ const QString &label() const { return mLabel; } /** * @param v Set the user visible string identifying this item. */ void setLabel( const QString &v ); /** * @return The string to be used for showing this item's current status. */ const QString &status() const { return mStatus; } /** * Set the string to be used for showing this item's current status. * @param v The status string. */ void setStatus( const QString &v ); /** * @return Whether this item can be canceled. */ bool canBeCanceled() const { return mCanBeCanceled; } /** * @return Whether this item uses secure communication * (Account uses ssl, for example.). */ bool usesCrypto() const { return mUsesCrypto; } /** * Set whether this item uses crypted communication, so listeners * can display a nice crypto icon. * @param v The value. */ void setUsesCrypto( bool v ); /** * @return whether this item uses a busy indicator instead of real progress display */ bool usesBusyIndicator() const { return mUsesBusyIndicator; } /** * Sets whether this item uses a busy indicator instead of real progress for its progress bar. * If it uses a busy indicator, you are still responsible for calling setProgress() from time to * time to update the busy indicator. */ void setUsesBusyIndicator( bool useBusyIndicator ); /** * @return The current progress value of this item in percent. */ unsigned int progress() const { return mProgress; } /** * Set the progress (percentage of completion) value of this item. * @param v The percentage value. */ void setProgress( unsigned int v ); /** * Tell the item it has finished. This will emit progressItemCompleted() * result in the destruction of the item after all slots connected to this * signal have executed. This is the only way to get rid of an item and * needs to be called even if the item is canceled. Don't use the item * after this has been called on it. */ void setComplete(); /** * Reset the progress value of this item to 0 and the status string to * the empty string. */ void reset() { setProgress( 0 ); setStatus( QString() ); mCompleted = 0; } void cancel(); // Often needed values for calculating progress. void setTotalItems( unsigned int v ) { mTotal = v; } unsigned int totalItems() const { return mTotal; } void setCompletedItems( unsigned int v ) { mCompleted = v; } void incCompletedItems( unsigned int v = 1 ) { mCompleted += v; } unsigned int completedItems() const { return mCompleted; } /** * Recalculate progress according to total/completed items and update. */ void updateProgress() { setProgress( mTotal? mCompleted * 100 / mTotal : 0 ); } void addChild( ProgressItem *kiddo ); void removeChild( ProgressItem *kiddo ); bool canceled() const { return mCanceled; } void setBusy( bool busy ); Q_SIGNALS: /** * Emitted when a new ProgressItem is added. * @param item The ProgressItem that was added. */ void progressItemAdded( KDevelop::ProgressItem* item ); /** * Emitted when the progress value of an item changes. * @param item The item which got a new value. * @param value The value, for convenience. */ void progressItemProgress( KDevelop::ProgressItem* item, unsigned int value ); /** * Emitted when a progress item was completed. The item will be * deleted afterwards, so slots connected to this are the last * chance to work with this item. * @param item The completed item. */ void progressItemCompleted( KDevelop::ProgressItem* item ); /** * Emitted when an item was canceled. It will _not_ go away immediately, * only when the owner sets it complete, which will usually happen. Can be * used to visually indicate the canceled status of an item. Should be used * by the owner of the item to make sure it is set completed even if it is * canceled. There is a ProgressManager::slotStandardCancelHandler which * simply sets the item completed and can be used if no other work needs to * be done on cancel. * @param item The canceled item; */ void progressItemCanceled( KDevelop::ProgressItem* item ); /** * Emitted when the status message of an item changed. Should be used by * progress dialogs to update the status message for an item. * @param item The updated item. * @param message The new message. */ void progressItemStatus( KDevelop::ProgressItem* item, const QString& message ); /** * Emitted when the label of an item changed. Should be used by * progress dialogs to update the label of an item. * @param item The updated item. * @param label The new label. */ void progressItemLabel( KDevelop::ProgressItem* item, const QString& label ); /** * Emitted when the crypto status of an item changed. Should be used by * progress dialogs to update the crypto indicator of an item. * @param item The updated item. * @param state The new state. */ void progressItemUsesCrypto( KDevelop::ProgressItem* item, bool state ); /** * Emitted when the busy indicator state of an item changes. Should be used * by progress dialogs so that they can adjust the display of the progress bar * to the new mode. * @param item The updated item * @param value True if the item uses a busy indicator now, false otherwise */ void progressItemUsesBusyIndicator( KDevelop::ProgressItem *item, bool value ); protected: /* Only to be used by our good friend the ProgressManager */ ProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool isCancellable, bool usesCrypto ); ~ProgressItem() override; private: const QString mId; QString mLabel; QString mStatus; QPointer mParent; const bool mCanBeCanceled; unsigned int mProgress; ProgressItemMap mChildren; unsigned int mTotal; unsigned int mCompleted; bool mWaitingForKids; bool mCanceled; bool mUsesCrypto; bool mUsesBusyIndicator; bool mCompletedCalled; }; struct ProgressManagerPrivate; /** * The ProgressManager singleton keeps track of all ongoing transactions * and notifies observers (progress dialogs) when their progress percent value * changes, when they are completed (by their owner), and when they are canceled. * Each ProgressItem emits those signals individually and the singleton * broadcasts them. Use the createProgressItem() statics to acquire an item * and then call ->setProgress( int percent ) on it every time you want to * update the item and ->setComplete() when the operation is done. This will * delete the item. Connect to the item's progressItemCanceled() signal to be * notified when the user cancels the transaction using one of the observing * progress dialogs or by calling item->cancel() in some other way. The owner * is responsible for calling setComplete() on the item, even if it is canceled. * Use the standardCancelHandler() slot if that is all you want to do on cancel. * * Note that if you request an item with a certain id and there is already * one with that id, there will not be a new one created but the existing * one will be returned. This is convenient for accessing items that are * needed regularly without the to store a pointer to them or to add child * items to parents by id. */ class ProgressManager : public QObject { Q_OBJECT friend struct ProgressManagerPrivate; public: ~ProgressManager() override; /** * @return The singleton instance of this class. */ static ProgressManager *instance(); /** * @return a unique id number which can be used to discern * an operation from all others going on at the same time. Use that * number as the id string for your progressItem to ensure it is unique. */ static QString createUniqueID() { return QString::number( ++uID ); } /** * Creates a ProgressItem with a unique id and the given label. * This is the simplest way to acquire a progress item. It will not * have a parent and will be set to be cancellable and not using crypto. */ static ProgressItem *createProgressItem( const QString &label ) { return instance()->createProgressItemImpl( nullptr, createUniqueID(), label, QString(), true, false ); } /** * Creates a new progressItem with the given parent, id, label and initial * status. * * @param parent Specify an already existing item as the parent of this one. * @param id Used to identify this operation for cancel and progress info. * @param label The text to be displayed by progress handlers * @param status Additional text to be displayed for the item. * @param canBeCanceled can the user cancel this operation? * @param usesCrypto does the operation use secure transports (SSL) * Cancelling the parent will cancel the children as well (if they can be * canceled) and ongoing children prevent parents from finishing. * @return The ProgressItem representing the operation. */ static ProgressItem *createProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemImpl( parent, id, label, status, canBeCanceled, usesCrypto ); } /** * Use this version if you have the id string of the parent and want to * add a subjob to it. */ static ProgressItem *createProgressItem( const QString &parent, const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemImpl( parent, id, label, status, canBeCanceled, usesCrypto ); } /** * Version without a parent. */ static ProgressItem *createProgressItem( const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemImpl( nullptr, id, label, status, canBeCanceled, usesCrypto ); } /** * Version for Akonadi agents. * This connects all the proper signals so that you do not have to * worry about updating the progress or reacting to progressItemCanceled(). */ static ProgressItem *createProgressItem( ProgressItem *parent, const Akonadi::AgentInstance &agent, const QString &id, const QString &label, const QString &status = QString(), bool canBeCanceled = true, bool usesCrypto = false ) { return instance()->createProgressItemForAgent( parent, agent, id, label, status, canBeCanceled, usesCrypto ); } /** * @return true when there are no more progress items. */ bool isEmpty() const { return mTransactions.isEmpty(); } /** * @return the only top level progressitem when there's only one. * Returns 0 if there is no item, or more than one top level item. * Since this is used to calculate the overall progress, it will also return * 0 if there is an item which uses a busy indicator, since that will invalidate * the overall progress. */ ProgressItem *singleItem() const; /** * Ask all listeners to show the progress dialog, because there is * something that wants to be shown. */ static void emitShowProgressDialog() { instance()->emitShowProgressDialogImpl(); } Q_SIGNALS: /** @see ProgressItem::progressItemAdded() */ void progressItemAdded( KDevelop::ProgressItem * ); /** @see ProgressItem::progressItemProgress() */ void progressItemProgress( KDevelop::ProgressItem *, unsigned int ); /** @see ProgressItem::progressItemCompleted() */ void progressItemCompleted( KDevelop::ProgressItem * ); /** @see ProgressItem::progressItemCanceled() */ void progressItemCanceled( KDevelop::ProgressItem * ); /** @see ProgressItem::progressItemStatus() */ void progressItemStatus( KDevelop::ProgressItem *, const QString & ); /** @see ProgressItem::progressItemLabel() */ void progressItemLabel( KDevelop::ProgressItem *, const QString & ); /** @see ProgressItem::progressItemUsesCrypto() */ void progressItemUsesCrypto( KDevelop::ProgressItem *, bool ); /** @see ProgressItem::progressItemUsesBusyIndicator */ void progressItemUsesBusyIndicator( KDevelop::ProgressItem*, bool ); /** * Emitted when an operation requests the listeners to be shown. * Use emitShowProgressDialog() to trigger it. */ void showProgressDialog(); public Q_SLOTS: /** * Calls setCompleted() on the item, to make sure it goes away. * Provided for convenience. * @param item the canceled item. */ void slotStandardCancelHandler( KDevelop::ProgressItem *item ); /** * Aborts all running jobs. Bound to "Esc" */ void slotAbortAll(); private Q_SLOTS: void slotTransactionCompleted( KDevelop::ProgressItem *item ); private: ProgressManager(); // prevent unsolicited copies ProgressManager( const ProgressManager & ); virtual ProgressItem *createProgressItemImpl( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ); virtual ProgressItem *createProgressItemImpl( const QString &parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ); ProgressItem *createProgressItemForAgent( ProgressItem *parent, const Akonadi::AgentInstance &instance, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ); void emitShowProgressDialogImpl(); QHash< QString, ProgressItem* > mTransactions; static unsigned int uID; }; } #endif // __KDevelop_PROGRESSMANAGER_H__ diff --git a/kdevplatform/shell/runcontroller.cpp b/kdevplatform/shell/runcontroller.cpp index eb817b9767..aa381e71b6 100644 --- a/kdevplatform/shell/runcontroller.cpp +++ b/kdevplatform/shell/runcontroller.cpp @@ -1,1039 +1,1039 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol 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 "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } -typedef QPair Target; +using Target = QPair; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); } QString id() const override { return QStringLiteral("debug"); } QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } QString name() const override { return i18n("Execute"); } }; class KDevelop::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QString() ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = nullptr; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = nullptr; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); auto* launch = static_cast(ilaunch); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = nullptr; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( const KSharedConfigPtr& cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); const QStringList configs = group.readEntry(Strings::LaunchConfigurationsListEntry(), QStringList()); for (const QString& cfg : configs) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return nullptr; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->contextItem = nullptr; d->executeMode = nullptr; d->debugMode = nullptr; d->profileMode = nullptr; d->unityLauncher = new UnityLauncher(this); d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName()); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() = default; void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = nullptr; delete d->profileMode; d->profileMode = nullptr; delete d->debugMode; d->debugMode = nullptr; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return nullptr; } auto* run = static_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), QString()); return nullptr; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog); d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::ALT + Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("debug") ); } } void RunController::slotProfile() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("profile") ); } } void RunController::slotExecute() { if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("execute") ); } } void KDevelop::RunController::showConfigurationDialog() const { LaunchConfigurationDialog dlg; dlg.exec(); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return nullptr; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = nullptr; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connext syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qCWarning(SHELL) << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { auto* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), QString(), nullptr, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qCWarning(SHELL) << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; const auto configsInternal = launchConfigurationsInternal(); configs.reserve(configsInternal.size()); for (LaunchConfiguration* config : configsInternal) { configs << config; } return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return nullptr; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if (auto dl = defaultLaunch()) { execute(runMode, dl); } else { qCWarning(SHELL) << "no default launch!"; } } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); auto* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension(Context* ctx, QWidget* parent) { d->launchAsInfo.clear(); d->contextItem = nullptr; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { auto* prjctx = static_cast(ctx); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes ) { KActionMenu* menu = new KActionMenu(i18n("%1 As...", mode->name() ), parent); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); auto* act = new QAction(menu); act->setText( type->name() ); qCDebug(SHELL) << "Connect " << i << "for action" << act->text() << "in mode" << mode->name(); connect( act, &QAction::triggered, this, [this, i] () { d->launchAs(i); } ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } else { delete menu; } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/kdevplatform/shell/sourceformattercontroller.h b/kdevplatform/shell/sourceformattercontroller.h index 5152f75e83..870c3af605 100644 --- a/kdevplatform/shell/sourceformattercontroller.h +++ b/kdevplatform/shell/sourceformattercontroller.h @@ -1,175 +1,175 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur 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 KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #include #include #include #include #include #include #include #include "shellexport.h" class QUrl; namespace KTextEditor { class Document; } namespace KDevelop { class Context; class ContextMenuExtension; class IDocument; class TextDocument; class ISourceFormatter; class IPlugin; struct SourceFormatter { KDevelop::ISourceFormatter* formatter; // style name -> style. style objects owned by this - typedef QMap StyleMap; + using StyleMap = QMap; StyleMap styles; // Get a list of supported mime types from the style map. QSet supportedMimeTypes() const { QSet supported; for ( auto style: styles ) { foreach ( auto& item, style->mimeTypes() ) { supported.insert(item.mimeType); } } return supported; } ~SourceFormatter() { qDeleteAll(styles); }; }; /** \short A singleton class managing all source formatter plugins */ class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient { Q_OBJECT friend class SourceFormatterJob; public: static QString kateModeLineConfigKey(); static QString kateOverrideIndentationConfigKey(); static QString styleCaptionKey(); static QString styleContentKey(); static QString styleMimeTypesKey(); static QString styleSampleKey(); explicit SourceFormatterController(QObject *parent = nullptr); ~SourceFormatterController() override; void initialize(); void cleanup(); //----------------- Public API defined in interfaces ------------------- /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ ISourceFormatter* formatterForUrl(const QUrl &url) override; /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * The source formatter is then ready to use on a file. */ ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) override; bool hasFormatters() const override; /** \return Whether this mime type is supported by any plugin. */ bool isMimeTypeSupported(const QMimeType& mime) override; /** * @brief Instantiate a Formatter for the given plugin and load its configuration. * * @param ifmt The ISourceFormatter interface of the plugin * @return KDevelop::SourceFormatter* the SourceFormatter instance for the plugin, including config items */ SourceFormatter* createFormatterForPlugin(KDevelop::ISourceFormatter* ifmt) const; /** * @brief Find the first formatter which supports a given mime type. */ ISourceFormatter* findFirstFormatterForMimeType(const QMimeType& mime) const; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent); KDevelop::SourceFormatterStyle styleForUrl(const QUrl& url, const QMimeType& mime) override; KConfigGroup configForUrl(const QUrl& url) const; KConfigGroup sessionConfig() const; KConfigGroup globalConfig() const; void settingsChanged(); void disableSourceFormatting(bool disable) override; bool sourceFormattingEnabled() override; QVector formatters() const; Q_SIGNALS: void formatterLoaded(KDevelop::ISourceFormatter* ifmt); void formatterUnloading(KDevelop::ISourceFormatter* ifmt); private Q_SLOTS: void updateFormatTextAction(); void beautifySource(); void beautifyLine(); void formatFiles(); void documentLoaded(const QPointer& doc); void pluginLoaded(KDevelop::IPlugin* plugin); void unloadingPlugin(KDevelop::IPlugin* plugin); private: /** \return A modeline string (to add at the end or the beginning of a file) * corresponding to the settings of the active language. */ QString addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType&); /** \return The name of kate indentation mode for the mime type. * examples are cstyle, python, etc. */ QString indentationMode(const QMimeType& mime); void formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime); // Adapts the mode of the editor regarding indentation-style void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, const QUrl& url, bool ignoreModeline = false); void resetUi(); private: const QScopedPointer d; }; } #endif // KDEVPLATFORM_SOURCEFORMATTERMANAGER_H diff --git a/kdevplatform/shell/sourceformatterselectionedit.cpp b/kdevplatform/shell/sourceformatterselectionedit.cpp index c0f7731b7a..2c12bfa1e1 100644 --- a/kdevplatform/shell/sourceformatterselectionedit.cpp +++ b/kdevplatform/shell/sourceformatterselectionedit.cpp @@ -1,578 +1,578 @@ /* This file is part of KDevelop * * Copyright (C) 2008 Cédric Pasteur * Copyright (C) 2017 Friedrich W. H. Kossebau * * 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 General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sourceformatterselectionedit.h" #include "ui_sourceformatterselectionedit.h" #include "sourceformattercontroller.h" #include "settings/editstyledialog.h" #include "debug.h" #include "core.h" #include "plugincontroller.h" #include #include #include // TODO: remove later #include #include #include #include #include #include #include #include #include #define STYLE_ROLE (Qt::UserRole+1) using namespace KDevelop; namespace { namespace Strings { QString userStylePrefix() { return QStringLiteral("User"); } } } struct LanguageSettings { QList mimetypes; QSet formatters; // weak pointers to selected formatter and style, no ownership KDevelop::SourceFormatter* selectedFormatter = nullptr; // Should never be zero KDevelop::SourceFormatterStyle* selectedStyle = nullptr; // TODO: can this be zero? Assume that not }; -typedef QMap LanguageMap; -typedef QMap FormatterMap; +using LanguageMap = QMap; +using FormatterMap = QMap; class KDevelop::SourceFormatterSelectionEditPrivate { public: Ui::SourceFormatterSelectionEdit ui; // Language name -> language settings LanguageMap languages; // formatter name -> formatter. Formatters owned by this FormatterMap formatters; KTextEditor::Document* document; KTextEditor::View* view; }; SourceFormatterSelectionEdit::SourceFormatterSelectionEdit(QWidget* parent) : QWidget(parent) , d(new SourceFormatterSelectionEditPrivate) { d->ui.setupUi(this); connect(d->ui.cbLanguages, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSelectionEdit::selectLanguage); connect(d->ui.cbFormatters, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSelectionEdit::selectFormatter); connect(d->ui.styleList, &QListWidget::currentRowChanged, this, &SourceFormatterSelectionEdit::selectStyle); connect(d->ui.btnDelStyle, &QPushButton::clicked, this, &SourceFormatterSelectionEdit::deleteStyle); connect(d->ui.btnNewStyle, &QPushButton::clicked, this, &SourceFormatterSelectionEdit::newStyle); connect(d->ui.btnEditStyle, &QPushButton::clicked, this, &SourceFormatterSelectionEdit::editStyle); connect(d->ui.styleList, &QListWidget::itemChanged, this, &SourceFormatterSelectionEdit::styleNameChanged); d->document = KTextEditor::Editor::instance()->createDocument(this); d->document->setReadWrite(false); d->view = d->document->createView(d->ui.textEditor); d->view->setStatusBarEnabled(false); auto *layout2 = new QVBoxLayout(d->ui.textEditor); layout2->setMargin(0); layout2->addWidget(d->view); d->ui.textEditor->setLayout(layout2); d->view->show(); KTextEditor::ConfigInterface *iface = qobject_cast(d->view); if (iface) { iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); } SourceFormatterController* controller = Core::self()->sourceFormatterControllerInternal(); connect(controller, &SourceFormatterController::formatterLoaded, this, &SourceFormatterSelectionEdit::addSourceFormatter); connect(controller, &SourceFormatterController::formatterUnloading, this, &SourceFormatterSelectionEdit::removeSourceFormatter); const auto& formatters = controller->formatters(); for (auto* formatter : formatters) { addSourceFormatter(formatter); } } SourceFormatterSelectionEdit::~SourceFormatterSelectionEdit() { qDeleteAll(d->formatters); } static void selectAvailableStyle(LanguageSettings& lang) { Q_ASSERT(!lang.selectedFormatter->styles.empty()); lang.selectedStyle = *lang.selectedFormatter->styles.begin(); } void SourceFormatterSelectionEdit::addSourceFormatter(ISourceFormatter* ifmt) { qCDebug(SHELL) << "Adding source formatter:" << ifmt->name(); SourceFormatter* formatter; FormatterMap::const_iterator iter = d->formatters.constFind(ifmt->name()); if (iter == d->formatters.constEnd()) { formatter = Core::self()->sourceFormatterControllerInternal()->createFormatterForPlugin(ifmt); d->formatters[ifmt->name()] = formatter; } else { qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "loading which was already seen before by SourceFormatterSelectionEdit"; return; } foreach ( const SourceFormatterStyle* style, formatter->styles ) { foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) { QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); if (!mime.isValid()) { qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "supports unknown mimetype entry" << item.mimeType; continue; } QString languageName = item.highlightMode; LanguageSettings& l = d->languages[languageName]; l.mimetypes.append(mime); l.formatters.insert( formatter ); // init selection if needed if (!l.selectedFormatter) { l.selectedFormatter = formatter; selectAvailableStyle(l); } } } resetUi(); } void SourceFormatterSelectionEdit::removeSourceFormatter(ISourceFormatter* ifmt) { qCDebug(SHELL) << "Removing source formatter:" << ifmt->name(); auto iter = d->formatters.find(ifmt->name()); if (iter == d->formatters.end()) { qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "unloading which was not seen before by SourceFormatterSelectionEdit"; return; } d->formatters.erase(iter); auto formatter = iter.value(); auto languageIter = d->languages.begin(); while (languageIter != d->languages.end()) { LanguageSettings& l = languageIter.value(); l.formatters.remove(formatter); if (l.formatters.isEmpty()) { languageIter = d->languages.erase(languageIter); } else { // reset selected formatter if needed if (l.selectedFormatter == formatter) { l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } ++languageIter; } } delete formatter; resetUi(); } void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config) { for (auto languageIter = d->languages.begin(); languageIter != d->languages.end(); ++languageIter) { // Pick the first appropriate mimetype for this language LanguageSettings& l = languageIter.value(); const QList mimetypes = l.mimetypes; for (const QMimeType& mimetype : mimetypes) { QStringList formatterAndStyleName = config.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts); FormatterMap::const_iterator formatterIter = d->formatters.constFind(formatterAndStyleName.first()); if (formatterIter == d->formatters.constEnd()) { qCDebug(SHELL) << "Reference to unknown formatter" << formatterAndStyleName.first(); Q_ASSERT(!l.formatters.empty()); // otherwise there should be no entry for 'name' l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } else { l.selectedFormatter = formatterIter.value(); SourceFormatter::StyleMap::const_iterator styleIter = l.selectedFormatter->styles.constFind(formatterAndStyleName.at( 1 )); if (styleIter == l.selectedFormatter->styles.constEnd()) { qCDebug(SHELL) << "No style" << formatterAndStyleName.at( 1 ) << "found for formatter" << formatterAndStyleName.first(); selectAvailableStyle(l); } else { l.selectedStyle = styleIter.value(); } } } if (!l.selectedFormatter) { Q_ASSERT(!l.formatters.empty()); l.selectedFormatter = *l.formatters.begin(); } if (!l.selectedStyle) { selectAvailableStyle(l); } } resetUi(); } void SourceFormatterSelectionEdit::resetUi() { qCDebug(SHELL) << "Resetting UI"; // Sort the languages, preferring firstly active, then loaded languages QList sortedLanguages; foreach(const auto language, KDevelop::ICore::self()->languageController()->activeLanguages() + KDevelop::ICore::self()->languageController()->loadedLanguages()) { if (d->languages.contains(language->name()) && !sortedLanguages.contains(language->name())) { sortedLanguages.push_back( language->name() ); } } foreach (const QString& name, d->languages.keys()) { if( !sortedLanguages.contains( name ) ) sortedLanguages.push_back( name ); } bool b = blockSignals( true ); d->ui.cbLanguages->blockSignals(!b); d->ui.cbFormatters->blockSignals(!b); d->ui.styleList->blockSignals(!b); d->ui.cbLanguages->clear(); d->ui.cbFormatters->clear(); d->ui.styleList->clear(); foreach( const QString& name, sortedLanguages ) { d->ui.cbLanguages->addItem(name); } if (d->ui.cbLanguages->count() == 0) { d->ui.cbLanguages->setEnabled(false); selectLanguage( -1 ); } else { d->ui.cbLanguages->setCurrentIndex(0); d->ui.cbLanguages->setEnabled(true); selectLanguage( 0 ); } updatePreview(); blockSignals( b ); d->ui.cbLanguages->blockSignals(b); d->ui.cbFormatters->blockSignals(b); d->ui.styleList->blockSignals(b); } void SourceFormatterSelectionEdit::saveSettings(KConfigGroup& config) { // store formatters globally KConfigGroup globalConfig = Core::self()->sourceFormatterControllerInternal()->globalConfig(); foreach (SourceFormatter* fmt, d->formatters) { KConfigGroup fmtgrp = globalConfig.group( fmt->formatter->name() ); // delete all styles so we don't leave any behind when all user styles are deleted foreach( const QString& subgrp, fmtgrp.groupList() ) { if( subgrp.startsWith( Strings::userStylePrefix() ) ) { fmtgrp.deleteGroup( subgrp ); } } foreach( const SourceFormatterStyle* style, fmt->styles ) { if( style->name().startsWith( Strings::userStylePrefix() ) ) { KConfigGroup stylegrp = fmtgrp.group( style->name() ); stylegrp.writeEntry( SourceFormatterController::styleCaptionKey(), style->caption() ); stylegrp.writeEntry( SourceFormatterController::styleContentKey(), style->content() ); stylegrp.writeEntry( SourceFormatterController::styleMimeTypesKey(), style->mimeTypesVariant() ); stylegrp.writeEntry( SourceFormatterController::styleSampleKey(), style->overrideSample() ); } } } globalConfig.sync(); // store selected formatters in given language for (const auto& setting : qAsConst(d->languages)) { for(const auto& mime : setting.mimetypes) { const QString formatterId = setting.selectedFormatter->formatter->name() + QLatin1String("||") + setting.selectedStyle->name(); config.writeEntry(mime.name(), formatterId); } } } void SourceFormatterSelectionEdit::enableStyleButtons() { bool userEntry = d->ui.styleList->currentItem() && d->ui.styleList->currentItem()->data(STYLE_ROLE).toString().startsWith(Strings::userStylePrefix()); QString languageName = d->ui.cbLanguages->currentText(); QMap::const_iterator it = d->languages.constFind(languageName); bool hasEditWidget = false; if (it != d->languages.constEnd()) { const LanguageSettings& l = it.value(); Q_ASSERT(l.selectedFormatter); ISourceFormatter* fmt = l.selectedFormatter->formatter; hasEditWidget = ( fmt && QScopedPointer(fmt->editStyleWidget( l.mimetypes.first() )) ); } d->ui.btnDelStyle->setEnabled(userEntry); d->ui.btnEditStyle->setEnabled(userEntry && hasEditWidget); d->ui.btnNewStyle->setEnabled(d->ui.cbFormatters->currentIndex() >= 0 && hasEditWidget); } void SourceFormatterSelectionEdit::selectLanguage( int idx ) { d->ui.cbFormatters->clear(); if( idx < 0 ) { d->ui.cbFormatters->setEnabled(false); selectFormatter( -1 ); return; } d->ui.cbFormatters->setEnabled(true); { QSignalBlocker blocker(d->ui.cbFormatters); LanguageSettings& l = d->languages[d->ui.cbLanguages->itemText(idx)]; foreach( const SourceFormatter* fmt, l.formatters ) { d->ui.cbFormatters->addItem(fmt->formatter->caption(), fmt->formatter->name()); } d->ui.cbFormatters->setCurrentIndex(d->ui.cbFormatters->findData(l.selectedFormatter->formatter->name())); } selectFormatter(d->ui.cbFormatters->currentIndex()); emit changed(); } void SourceFormatterSelectionEdit::selectFormatter( int idx ) { d->ui.styleList->clear(); if( idx < 0 ) { d->ui.styleList->setEnabled(false); enableStyleButtons(); return; } d->ui.styleList->setEnabled(true); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; Q_ASSERT( idx < l.formatters.size() ); FormatterMap::const_iterator formatterIter = d->formatters.constFind(d->ui.cbFormatters->itemData(idx).toString()); Q_ASSERT( formatterIter != d->formatters.constEnd() ); Q_ASSERT( l.formatters.contains(formatterIter.value()) ); if (l.selectedFormatter != formatterIter.value()) { l.selectedFormatter = formatterIter.value(); l.selectedStyle = nullptr; // will hold 0 until a style is picked } foreach( const SourceFormatterStyle* style, formatterIter.value()->styles ) { if (!style->supportsLanguage(d->ui.cbLanguages->currentText())) { // do not list items which do not support the selected language continue; } QListWidgetItem* item = addStyle( *style ); if (style == l.selectedStyle) { d->ui.styleList->setCurrentItem(item); } } if (l.selectedStyle == nullptr) { d->ui.styleList->setCurrentRow(0); } enableStyleButtons(); emit changed(); } void SourceFormatterSelectionEdit::selectStyle( int row ) { if( row < 0 ) { enableStyleButtons(); return; } d->ui.styleList->setCurrentRow(row); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; l.selectedStyle = l.selectedFormatter->styles[d->ui.styleList->item(row)->data(STYLE_ROLE).toString()]; enableStyleButtons(); updatePreview(); emit changed(); } void SourceFormatterSelectionEdit::deleteStyle() { Q_ASSERT( d->ui.styleList->currentRow() >= 0 ); QListWidgetItem* item = d->ui.styleList->currentItem(); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatter::StyleMap::iterator styleIter = fmt->styles.find(item->data( STYLE_ROLE ).toString()); QStringList otherLanguageNames; QList otherlanguages; for (LanguageMap::iterator languageIter = d->languages.begin(); languageIter != d->languages.end(); ++languageIter) { if ( &languageIter.value() != &l && languageIter.value().selectedStyle == styleIter.value() ) { otherLanguageNames.append(languageIter.key()); otherlanguages.append(&languageIter.value()); } } if (!otherLanguageNames.empty() && KMessageBox::warningContinueCancel(this, i18n("The style %1 is also used for the following languages:\n%2.\nAre you sure you want to delete it?", styleIter.value()->caption(), otherLanguageNames.join(QLatin1Char('\n'))), i18n("Style being deleted")) != KMessageBox::Continue) { return; } d->ui.styleList->takeItem(d->ui.styleList->currentRow()); fmt->styles.erase(styleIter); delete item; selectStyle(d->ui.styleList->count() > 0 ? 0 : -1); foreach (LanguageSettings* lang, otherlanguages) { selectAvailableStyle(*lang); } updatePreview(); emit changed(); } void SourceFormatterSelectionEdit::editStyle() { QString language = d->ui.cbLanguages->currentText(); Q_ASSERT( d->languages.contains(language) ); LanguageSettings& l = d->languages[language]; SourceFormatter* fmt = l.selectedFormatter; QMimeType mimetype = l.mimetypes.first(); if( QScopedPointer(fmt->formatter->editStyleWidget( mimetype )) ) { KDevelop::ScopedDialog dlg(fmt->formatter, mimetype, *l.selectedStyle, this); if( dlg->exec() == QDialog::Accepted ) { l.selectedStyle->setContent(dlg->content()); } updatePreview(); emit changed(); } } void SourceFormatterSelectionEdit::newStyle() { QListWidgetItem* item = d->ui.styleList->currentItem(); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; SourceFormatter* fmt = l.selectedFormatter; int idx = 0; for (int i = 0; i < d->ui.styleList->count(); ++i) { QString name = d->ui.styleList->item(i)->data(STYLE_ROLE).toString(); if( name.startsWith( Strings::userStylePrefix() ) && name.midRef( Strings::userStylePrefix().length() ).toInt() >= idx ) { idx = name.midRef( Strings::userStylePrefix().length() ).toInt(); } } // Increase number for next style idx++; SourceFormatterStyle* s = new SourceFormatterStyle( QStringLiteral( "%1%2" ).arg( Strings::userStylePrefix() ).arg( idx ) ); if( item ) { SourceFormatterStyle* existstyle = fmt->styles[ item->data( STYLE_ROLE ).toString() ]; s->setCaption( i18n( "New %1", existstyle->caption() ) ); s->copyDataFrom( existstyle ); } else { s->setCaption( i18n( "New Style" ) ); } fmt->styles[ s->name() ] = s; QListWidgetItem* newitem = addStyle( *s ); selectStyle(d->ui.styleList->row(newitem)); d->ui.styleList->editItem(newitem); emit changed(); } void SourceFormatterSelectionEdit::styleNameChanged( QListWidgetItem* item ) { if ( !item->isSelected() ) { return; } LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; l.selectedStyle->setCaption( item->text() ); emit changed(); } QListWidgetItem* SourceFormatterSelectionEdit::addStyle( const SourceFormatterStyle& s ) { auto* item = new QListWidgetItem(d->ui.styleList); item->setText( s.caption() ); item->setData( STYLE_ROLE, s.name() ); if( s.name().startsWith( Strings::userStylePrefix() ) ) { item->setFlags( item->flags() | Qt::ItemIsEditable ); } d->ui.styleList->addItem(item); return item; } void SourceFormatterSelectionEdit::updatePreview() { d->document->setReadWrite(true); QString langName = d->ui.cbLanguages->itemText(d->ui.cbLanguages->currentIndex()); if( !langName.isEmpty() ) { LanguageSettings& l = d->languages[langName]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatterStyle* style = l.selectedStyle; d->ui.descriptionLabel->setText(style->description()); if( style->usePreview() ) { ISourceFormatter* ifmt = fmt->formatter; QMimeType mime = l.mimetypes.first(); d->document->setHighlightingMode(style->modeForMimetype(mime)); //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 KTextEditor::ConfigInterface* iface = qobject_cast(d->document); QVariant oldReplaceTabs; if (iface) { oldReplaceTabs = iface->configValue(QStringLiteral("replace-tabs")); iface->setConfigValue(QStringLiteral("replace-tabs"), false); } d->document->setText(ifmt->formatSourceWithStyle(*style, ifmt->previewText(*style, mime), QUrl(), mime)); if (iface) { iface->setConfigValue(QStringLiteral("replace-tabs"), oldReplaceTabs); } d->ui.previewLabel->show(); d->ui.textEditor->show(); }else{ d->ui.previewLabel->hide(); d->ui.textEditor->hide(); } } else { d->document->setText(i18n("No language selected")); } d->view->setCursorPosition(KTextEditor::Cursor(0, 0)); d->document->setReadWrite(false); } diff --git a/kdevplatform/tests/json/delayedoutput.cpp b/kdevplatform/tests/json/delayedoutput.cpp index 69c09237ae..8f822c2b57 100644 --- a/kdevplatform/tests/json/delayedoutput.cpp +++ b/kdevplatform/tests/json/delayedoutput.cpp @@ -1,69 +1,69 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 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 "delayedoutput.h" #include #include namespace KDevelop { -typedef QPair DepthedOutput; +using DepthedOutput = QPair; class DelayedOutputPrivate { public: void flushOutput() { while (!output.isEmpty()) { DepthedOutput curOutput = output.pop(); qDebug().nospace() << qPrintable(QString(curOutput.second - 1, QLatin1Char(' '))) << curOutput.first.toUtf8().data(); } } QStack output; int delayDepth; }; DelayedOutput::Delay::Delay(DelayedOutput* output) { m_output = output; ++m_output->d->delayDepth; } DelayedOutput::Delay::~Delay() { --m_output->d->delayDepth; if (!m_output->d->delayDepth) m_output->d->flushOutput(); } DelayedOutput::DelayedOutput() : d(new DelayedOutputPrivate()) { } DelayedOutput::~DelayedOutput() { } DelayedOutput& DelayedOutput::self() { static DelayedOutput _inst; return _inst; } void DelayedOutput::push(const QString& output) { d->output.push(DepthedOutput(output, d->delayDepth)); } } diff --git a/kdevplatform/tests/json/testsuite.h b/kdevplatform/tests/json/testsuite.h index 20f5b2e87d..33360ab913 100644 --- a/kdevplatform/tests/json/testsuite.h +++ b/kdevplatform/tests/json/testsuite.h @@ -1,134 +1,134 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 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 KDEVPLATFORM_TESTSUITE_H #define KDEVPLATFORM_TESTSUITE_H #include #include "delayedoutput.h" #include #include namespace KDevelop { class DUContext; class Declaration; inline QString EXPECT_FAIL() { return QStringLiteral("EXPECT_FAIL"); } inline QString FAILED_TO_FAIL() { return QStringLiteral("\"%1\" FAILED TO FAIL AS EXPECTED: \"%2\" %3"); } inline QString EXPECTED_FAIL() { return QStringLiteral("\"%1\" FAILED (expected): %2 %3"); } inline QString FAIL() { return QStringLiteral("\"%1\" FAILED: %2 %3"); } inline QString TEST_NOT_FOUND() { return QStringLiteral("Test not found"); } template class TestSuite; KDEVPLATFORMTESTS_EXPORT TestSuite& declarationTestSuite(); KDEVPLATFORMTESTS_EXPORT TestSuite& contextTestSuite(); KDEVPLATFORMTESTS_EXPORT TestSuite& typeTestSuite(); template class KDEVPLATFORMTESTS_EXPORT TestSuite { public: - typedef QString (* TestFunction)(const QVariant&, T); + using TestFunction = QString (*)(const QVariant&, T); static TestSuite& get(); bool addTest(const QString& testName, TestFunction testFunc) { m_testFunctions.insert(testName, testFunc); return true; } bool runTests(const QVariantMap& testData, T object) { QVariantMap expectedFails = expectedFailures(testData); QVariantMap::const_iterator it; DelayedOutput::Delay delay(&DelayedOutput::self()); for (it = testData.begin(); it != testData.end(); ++it) { if (it.key() == EXPECT_FAIL()) continue; QString result = m_testFunctions.value(it.key(), &TestSuite::noSuchTest)(it.value(), object); QString expectedFailure = expectedFails.value(it.key(), QString()).toString(); //Either ("expected failure" & "no result failure") or ("no expected failure" & "result failure") if (expectedFailure.isEmpty() ^ result.isEmpty()) { DelayedOutput::self().push(result.isEmpty() ? FAILED_TO_FAIL().arg(it.key(), expectedFailure, objectInformation(object)) : FAIL().arg(it.key(), result, objectInformation(object))); return false; } if (!expectedFailure.isEmpty()) qDebug() << EXPECTED_FAIL().arg(it.key(), expectedFailure, objectInformation(object)).toUtf8().data(); } return true; } private: QVariantMap expectedFailures(const QVariantMap& testData) { if (!testData.contains(EXPECT_FAIL())) return QVariantMap(); return testData[EXPECT_FAIL()].toMap(); } static QString noSuchTest(const QVariant&, T) { return TEST_NOT_FOUND(); } static QString objectInformation(T) { return QString(); } QHash m_testFunctions; TestSuite() { } Q_DISABLE_COPY(TestSuite) friend TestSuite& declarationTestSuite(); friend TestSuite& contextTestSuite(); friend TestSuite& typeTestSuite(); }; template inline bool runTests(const QVariantMap& data, T object) { return TestSuite::get().runTests(data, object); } ///TODO: Once we can use C++11, see whether this can be cleaned up by extern templates template<> inline TestSuite& TestSuite::get() { return declarationTestSuite(); } template<> inline TestSuite& TestSuite::get() { return contextTestSuite(); } template<> inline TestSuite& TestSuite::get() { return typeTestSuite(); } } #endif //KDEVPLATFORM_TESTSUITE_H diff --git a/kdevplatform/util/activetooltip.cpp b/kdevplatform/util/activetooltip.cpp index 40e54245a8..81d87cccad 100644 --- a/kdevplatform/util/activetooltip.cpp +++ b/kdevplatform/util/activetooltip.cpp @@ -1,337 +1,337 @@ /* This file is part of the KDE project Copyright 2007 Vladimir Prus Copyright 2009-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 "activetooltip.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { class ActiveToolTipManager : public QObject { Q_OBJECT public Q_SLOTS: void doVisibility(); public: - typedef QMultiMap, QString>> ToolTipPriorityMap; + using ToolTipPriorityMap = QMultiMap, QString>>; ToolTipPriorityMap registeredToolTips; }; ActiveToolTipManager* manager() { static ActiveToolTipManager m; return &m; } QWidget* masterWidget(QWidget* w) { while (w && w->parent() && qobject_cast(w->parent())) { w = qobject_cast(w->parent()); } return w; } void ActiveToolTipManager::doVisibility() { bool exclusive = false; int lastBottomPosition = -1; int lastLeftPosition = -1; QRect fullGeometry; //Geometry of all visible tooltips together for (auto it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) { QPointer w = (*it).first; if (w) { if (exclusive) { (w.data())->hide(); } else { QRect geom = (w.data())->geometry(); if ((w.data())->geometry().top() < lastBottomPosition) { geom.moveTop(lastBottomPosition); } if (lastLeftPosition != -1) { geom.moveLeft(lastLeftPosition); } (w.data())->setGeometry(geom); lastBottomPosition = (w.data())->geometry().bottom(); lastLeftPosition = (w.data())->geometry().left(); if (it == registeredToolTips.constBegin()) { fullGeometry = (w.data())->geometry(); } else { fullGeometry = fullGeometry.united((w.data())->geometry()); } } if (it.key() == 0) { exclusive = true; } } } if (!fullGeometry.isEmpty()) { QRect oldFullGeometry = fullGeometry; QRect screenGeometry = QApplication::desktop()->screenGeometry(fullGeometry.topLeft()); if (fullGeometry.bottom() > screenGeometry.bottom()) { //Move up, avoiding the mouse-cursor fullGeometry.moveBottom(fullGeometry.top() - 10); if (fullGeometry.adjusted(-20, -20, 20, 20).contains(QCursor::pos())) { fullGeometry.moveBottom(QCursor::pos().y() - 20); } } if (fullGeometry.right() > screenGeometry.right()) { //Move to left, avoiding the mouse-cursor fullGeometry.moveRight(fullGeometry.left() - 10); if (fullGeometry.adjusted(-20, -20, 20, 20).contains(QCursor::pos())) { fullGeometry.moveRight(QCursor::pos().x() - 20); } } // Now fit this to screen if (fullGeometry.left() < 0) { fullGeometry.setLeft(0); } if (fullGeometry.top() < 0) { fullGeometry.setTop(0); } QPoint offset = fullGeometry.topLeft() - oldFullGeometry.topLeft(); if (!offset.isNull()) { for (auto it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) if (it->first) { it->first.data()->move(it->first.data()->pos() + offset); } } } //Always include the mouse cursor in the full geometry, to avoid //closing the tooltip inexpectedly fullGeometry = fullGeometry.united(QRect(QCursor::pos(), QCursor::pos())); //Set bounding geometry, and remove old tooltips for (auto it = registeredToolTips.begin(); it != registeredToolTips.end();) { if (!it->first) { it = registeredToolTips.erase(it); } else { it->first.data()->setBoundingGeometry(fullGeometry); ++it; } } //Final step: Show tooltips foreach (const auto& tooltip, registeredToolTips) { if (tooltip.first.data() && masterWidget(tooltip.first.data())->isActiveWindow()) { tooltip.first.data()->show(); } if (exclusive) { break; } } } } namespace KDevelop { class ActiveToolTipPrivate { public: QRect rect_; QRect handleRect_; QVector> friendWidgets_; }; } ActiveToolTip::ActiveToolTip(QWidget* parent, const QPoint& position) : QWidget(parent, Qt::ToolTip) , d(new ActiveToolTipPrivate) { Q_ASSERT(parent); setMouseTracking(true); d->rect_ = QRect(position, position); d->rect_.adjust(-10, -10, 10, 10); move(position); QPalette p; // adjust background color to use tooltip colors p.setColor(backgroundRole(), p.color(QPalette::ToolTipBase)); p.setColor(QPalette::Base, p.color(QPalette::ToolTipBase)); // adjust foreground color to use tooltip colors p.setColor(foregroundRole(), p.color(QPalette::ToolTipText)); p.setColor(QPalette::Text, p.color(QPalette::ToolTipText)); setPalette(p); setWindowFlags(Qt::WindowDoesNotAcceptFocus | windowFlags()); qApp->installEventFilter(this); } ActiveToolTip::~ActiveToolTip() = default; bool ActiveToolTip::eventFilter(QObject* object, QEvent* e) { switch (e->type()) { case QEvent::MouseMove: if (underMouse() || insideThis(object)) { return false; } else { QPoint globalPos = static_cast(e)->globalPos(); QRect mergedRegion = d->rect_.united(d->handleRect_); if (mergedRegion.contains(globalPos)) { return false; } close(); } break; case QEvent::WindowActivate: if (insideThis(object)) { return false; } close(); break; case QEvent::WindowBlocked: // Modal dialog activated somewhere, it is the only case where a cursor // move may be missed and the popup has to be force-closed close(); break; default: break; } return false; } void ActiveToolTip::addFriendWidget(QWidget* widget) { d->friendWidgets_.append(( QObject* )widget); } bool ActiveToolTip::insideThis(QObject* object) { while (object) { if (dynamic_cast(object)) { return true; } if (object == this || object == ( QObject* )this->windowHandle() || d->friendWidgets_.contains(object)) { return true; } object = object->parent(); } // If the object clicked is inside a QQuickWidget, its parent is null even // if it is part of a tool-tip. This check ensures that a tool-tip is never // closed while the mouse is in it return underMouse(); } void ActiveToolTip::showEvent(QShowEvent*) { adjustRect(); } void ActiveToolTip::resizeEvent(QResizeEvent*) { adjustRect(); // set mask from style QStyleOptionFrame opt; opt.init(this); QStyleHintReturnMask mask; if (style()->styleHint(QStyle::SH_ToolTip_Mask, &opt, this, &mask) && !mask.region.isEmpty()) { setMask(mask.region); } emit resized(); } void ActiveToolTip::paintEvent(QPaintEvent* event) { QStylePainter painter(this); painter.setClipRegion(event->region()); QStyleOptionFrame opt; opt.init(this); painter.drawPrimitive(QStyle::PE_PanelTipLabel, opt); } void ActiveToolTip::setHandleRect(const QRect& rect) { d->handleRect_ = rect; } void ActiveToolTip::adjustRect() { // For tooltip widget, geometry() returns global coordinates. QRect r = geometry(); r.adjust(-10, -10, 10, 10); d->rect_ = r; } void ActiveToolTip::setBoundingGeometry(const QRect& geometry) { d->rect_ = geometry; d->rect_.adjust(-10, -10, 10, 10); } void ActiveToolTip::showToolTip(ActiveToolTip* tooltip, float priority, const QString& uniqueId) { auto& registeredToolTips = manager()->registeredToolTips; if (!uniqueId.isEmpty()) { foreach (const auto& tooltip, registeredToolTips) { if (tooltip.second == uniqueId) { delete tooltip.first.data(); } } } registeredToolTips.insert(priority, qMakePair(QPointer(tooltip), uniqueId)); connect(tooltip, &ActiveToolTip::resized, manager(), &ActiveToolTipManager::doVisibility); QMetaObject::invokeMethod(manager(), "doVisibility", Qt::QueuedConnection); } void ActiveToolTip::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); deleteLater(); } #include "activetooltip.moc" diff --git a/kdevplatform/util/convenientfreelist.h b/kdevplatform/util/convenientfreelist.h index f55accc7ba..c219938bce 100644 --- a/kdevplatform/util/convenientfreelist.h +++ b/kdevplatform/util/convenientfreelist.h @@ -1,831 +1,831 @@ /* This file is part of KDevelop 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. */ #ifndef KDEVPLATFORM_CONVENIENTFREELIST_H #define KDEVPLATFORM_CONVENIENTFREELIST_H #include #include #include "embeddedfreetree.h" #include "kdevvarlengtharray.h" namespace KDevelop { template class ConvenientEmbeddedSetIterator; template class ConvenientEmbeddedSetFilterIterator; ///A convenience-class for accessing the data in a set managed by the EmbeddedFreeTree algorithms. template class ConstantConvenientEmbeddedSet { public: ConstantConvenientEmbeddedSet() : m_data(nullptr) { } ConstantConvenientEmbeddedSet(const Data* data, uint count, int centralFreeItem) : m_data(data) , m_dataSize(count) , m_centralFreeItem(centralFreeItem) { } ///Returns whether the item is contained in this set bool contains(const Data& data) const { return indexOf(data) != -1; } ///Returns the position of the item in the underlying array, or -1 if it is not contained int indexOf(const Data& data) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.indexOf(data); } ///Returns the size of the underlying array uint dataSize() const { return m_dataSize; } uint countFreeItems() { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.countFreeItems(); } void verify() { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); alg.verifyTreeConsistent(); alg.verifyOrder(); } ///Returns the underlying array. That array may contain invalid/free items. const Data* data() const { return m_data; } ///Returns the first valid index that has a data-value larger or equal to @p data. ///Returns -1 if nothing is found. int lowerBound(const Data& data) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.lowerBound(data, 0, m_dataSize); } ///Returns the first valid index that has a data-value larger or equal to @p data, ///and that is in the range [start, end). ///Returns -1 if nothing is found. int lowerBound(const Data& data, uint start, uint end) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.lowerBound(data, start, end); } ///Finds a valid most central in the range [start, end). ///Returns -1 if no such item exists. int validMiddle(uint start, uint end) { if (end <= start) return -1; int firstTry = ((end - start) / 2) + start; int thisTry = firstTry; while (thisTry < ( int )end && Handler::isFree(m_data[thisTry])) ++thisTry; if (thisTry != ( int )end) return thisTry; //Nothing find on right side of middle, try the other direction thisTry = firstTry - 1; while (thisTry >= ( int )start && Handler::isFree(m_data[thisTry])) --thisTry; if (thisTry >= ( int )start) return thisTry; else return -1; } ///Returns the first valid item in the range [pos, end), or -1 int firstValidItem(int start, int end = -1) const { if (end == -1) end = ( int )m_dataSize; for (; start < end; ++start) if (!Handler::isFree(m_data[start])) return start; return -1; } ///Returns the last valid item in the range [pos, end), or -1 int lastValidItem(int start = 0, int end = -1) const { if (end == -1) end = ( int )m_dataSize; --end; for (; end >= start; --end) if (!Handler::isFree(m_data[end])) return end; return -1; } - typedef ConvenientEmbeddedSetIterator Iterator; + using Iterator = ConvenientEmbeddedSetIterator; ConvenientEmbeddedSetIterator iterator() const; // protected: const Data* m_data; uint m_dataSize = 0; int m_centralFreeItem = -1; }; ///Convenient iterator that automatically skips invalid/free items in the array. template class ConvenientEmbeddedSetIterator : public ConstantConvenientEmbeddedSet { public: explicit ConvenientEmbeddedSetIterator(const Data* data = nullptr, uint count = 0, int centralFreeItem = -1) : ConstantConvenientEmbeddedSet( data, count, centralFreeItem) { //Move to first valid position moveToValid(); } ///Returns true of this iterator has a value to return operator bool() const { return m_pos != this->m_dataSize; } const Data* operator->() const { return &this->m_data[m_pos]; } const Data& operator*() const { return this->m_data[m_pos]; } ConvenientEmbeddedSetIterator& operator++() { ++m_pos; moveToValid(); return *this; } protected: inline void moveToValid() { while (this->m_pos < this->m_dataSize && (Handler::isFree(this->m_data[this->m_pos]))) ++this->m_pos; } uint m_pos = 0; }; ///An iterator that allows efficient matching between two lists with different data type. ///Important: It must be possible to extract the data-type of the second list from the items in the first list ///The second list must be sorted by that data. ///The first list must primarily be sorted by that data, but is allowed to ///be sub-ordered by something else, and multiple items in the first list are allowed to match one item in the second. ///This iterator iterates through all items in the first list that have extracted key-data that is in represented in the second. template class ConvenientEmbeddedSetFilterIterator : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetFilterIterator() { } ConvenientEmbeddedSetFilterIterator(const ConvenientEmbeddedSetIterator& base, const ConvenientEmbeddedSetIterator& rhs) : ConvenientEmbeddedSetIterator(base) , m_rhs(rhs) , m_match(-1) { boundStack.append(qMakePair(qMakePair(0u, this->m_dataSize), qMakePair(0u, rhs.m_dataSize))); go(); } operator bool() const { return m_match != -1; } const Data* operator->() const { Q_ASSERT(m_match != -1); return &this->m_data[m_match]; } const Data& operator*() const { Q_ASSERT(m_match != -1); return this->m_data[m_match]; } ConvenientEmbeddedSetFilterIterator& operator++() { Q_ASSERT(m_match != -1); go(); return *this; } #define CHECK_BOUNDS Q_ASSERT( \ boundStack.back().first.first < 100000 && boundStack.back().first.second < 10000 && boundStack.back().second.first < 100000 && \ boundStack.back().second.second < 10000); private: void go() { m_match = -1; boundsUp: if (boundStack.isEmpty()) return; CHECK_BOUNDS QPair, QPair> currentBounds = boundStack.back(); boundStack.pop_back(); uint ownStart = currentBounds.first.first, ownEnd = currentBounds.first.second; uint rhsStart = currentBounds.second.first, rhsEnd = currentBounds.second.second; #if 0 //This code works, but it doesn't give a speedup int ownFirstValid = this->firstValidItem(ownStart, ownEnd), ownLastValid = this->lastValidItem(ownStart, ownEnd); int rhsFirstValid = m_rhs.firstValidItem(rhsStart, rhsEnd), rhsLastValid = m_rhs.lastValidItem(rhsStart, rhsEnd); if (ownFirstValid == -1 || rhsFirstValid == -1) goto boundsUp; Data2 ownFirstValidData = KeyExtractor::extract(this->m_data[ownFirstValid]); Data2 ownLastValidData = KeyExtractor::extract(this->m_data[ownLastValid]); Data2 commonStart = ownFirstValidData; Data2 commonLast = ownLastValidData; //commonLast is also still valid if (commonStart < m_rhs.m_data[rhsFirstValid]) commonStart = m_rhs.m_data[rhsFirstValid]; if (m_rhs.m_data[rhsLastValid] < commonLast) commonLast = m_rhs.m_data[rhsLastValid]; if (commonLast < commonStart) goto boundsUp; #endif while (true) { if (ownStart == ownEnd) goto boundsUp; int ownMiddle = this->validMiddle(ownStart, ownEnd); Q_ASSERT(ownMiddle < 100000); if (ownMiddle == -1) goto boundsUp; //No valid items in the range Data2 currentData2 = KeyExtractor::extract(this->m_data[ownMiddle]); Q_ASSERT(!Handler2::isFree(currentData2)); int bound = m_rhs.lowerBound(currentData2, rhsStart, rhsEnd); if (bound == -1) { //Release second half of the own range // Q_ASSERT(ownEnd > ownMiddle); ownEnd = ownMiddle; continue; } if (currentData2 == m_rhs.m_data[bound]) { //We have a match this->m_match = ownMiddle; //Append the ranges that need to be matched next, without the matched item boundStack.append(qMakePair(qMakePair(( uint )ownMiddle + 1, ownEnd), qMakePair(( uint )bound, rhsEnd))); if (ownMiddle) boundStack.append(qMakePair(qMakePair(ownStart, ( uint )ownMiddle), qMakePair(rhsStart, ( uint )bound + 1))); return; } if (bound == m_rhs.firstValidItem(rhsStart)) { //The bound is the first valid item of the second range. //Discard left side and the matched left item, and continue. ownStart = ownMiddle + 1; rhsStart = bound; continue; } //Standard: Split both sides into 2 ranges that will be checked next boundStack.append(qMakePair(qMakePair(( uint )ownMiddle + 1, ownEnd), qMakePair(( uint )bound, rhsEnd))); // Q_ASSERT(ownMiddle <= ownEnd); ownEnd = ownMiddle; //We loose the item at 'middle' here, but that's fine, since it hasn't found a match. rhsEnd = bound + 1; } } //Bounds that yet need to be matched. KDevVarLengthArray, QPair>> boundStack; ConvenientEmbeddedSetIterator m_rhs; int m_match = -1; }; ///Filters a list-embedded set by a binary tree set as managed by the SetRepository data structures template class ConvenientEmbeddedSetTreeFilterIterator : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetTreeFilterIterator() { } ///@param noFiltering whether the given input is pre-filtered. If this is true, base will be iterated without skipping any items. ConvenientEmbeddedSetTreeFilterIterator(const ConvenientEmbeddedSetIterator& base, const TreeSet& rhs, bool noFiltering = false) : ConvenientEmbeddedSetIterator( base) , m_rhs(rhs) , m_match(-1) , m_noFiltering(noFiltering) { if (rhs.node().isValid()) { //Correctly initialize the initial bounds int ownStart = lowerBound(rhs.node().firstItem(), 0, this->m_dataSize); if (ownStart == -1) return; int ownEnd = lowerBound(rhs.node().lastItem(), ownStart, this->m_dataSize); if (ownEnd == -1) ownEnd = this->m_dataSize; else ownEnd += 1; boundStack.append(qMakePair(qMakePair(( uint )ownStart, ( uint )ownEnd), rhs.node())); } go(); } operator bool() const { return m_match != -1; } const Data* operator->() const { Q_ASSERT(m_match != -1); return &this->m_data[m_match]; } const Data& operator*() const { Q_ASSERT(m_match != -1); return this->m_data[m_match]; } ConvenientEmbeddedSetTreeFilterIterator& operator++() { Q_ASSERT(m_match != -1); go(); return *this; } #define CHECK_BOUNDS Q_ASSERT( \ boundStack.back().first.first < 100000 && boundStack.back().first.second < 10000 && boundStack.back().second.first < 100000 && \ boundStack.back().second.second < 10000); private: void go() { if (m_noFiltering) { ++m_match; if (( uint )m_match >= this->m_dataSize) m_match = -1; return; } if (m_match != -1) { //Match multiple items in this list to one in the tree m_match = this->firstValidItem(m_match + 1, this->m_dataSize); if (m_match != -1 && KeyExtractor::extract(this->m_data[m_match]) == m_matchingTo) return; } m_match = -1; boundsUp: if (boundStack.isEmpty()) return; QPair, typename TreeSet::Node> currentBounds = boundStack.back(); boundStack.pop_back(); uint ownStart = currentBounds.first.first, ownEnd = currentBounds.first.second; typename TreeSet::Node currentNode = currentBounds.second; if (ownStart >= ownEnd) goto boundsUp; if (!currentNode.isValid()) goto boundsUp; while (true) { if (ownStart == ownEnd) goto boundsUp; if (currentNode.isFinalNode()) { // qCDebug(UTIL) << ownStart << ownEnd << "final node" << currentNode.start() * extractor_div_with << currentNode.end() * extractor_div_with; //Check whether the item is contained int bound = lowerBound(*currentNode, ownStart, ownEnd); // qCDebug(UTIL) << "bound:" << bound << (KeyExtractor::extract(this->m_data[bound]) == *currentNode); if (bound != -1 && KeyExtractor::extract(this->m_data[bound]) == *currentNode) { //Got a match m_match = bound; m_matchingTo = *currentNode; m_matchBound = ownEnd; return; } else { //Mismatch goto boundsUp; } } else { // qCDebug(UTIL) << ownStart << ownEnd << "node" << currentNode.start() * extractor_div_with << currentNode.end() * extractor_div_with; //This is not a final node, split up the search into the sub-nodes typename TreeSet::Node leftNode = currentNode.leftChild(); typename TreeSet::Node rightNode = currentNode.rightChild(); Q_ASSERT(leftNode.isValid()); Q_ASSERT(rightNode.isValid()); Data2 leftLastItem = leftNode.lastItem(); int rightSearchStart = lowerBound(rightNode.firstItem(), ownStart, ownEnd); if (rightSearchStart == -1) rightSearchStart = ownEnd; int leftSearchLast = lowerBound(leftLastItem, ownStart, rightSearchStart != -1 ? rightSearchStart : ownEnd); if (leftSearchLast == -1) leftSearchLast = rightSearchStart - 1; bool recurseLeft = false; if (leftSearchLast > ( int )ownStart) { recurseLeft = true; //There must be something in the range ownStart -> leftSearchLast that matches the range } else if (( int )ownStart == leftSearchLast) { //Check if the one item item under leftSearchStart is contained in the range Data2 leftFoundStartData = KeyExtractor::extract(this->m_data[ownStart]); recurseLeft = leftFoundStartData < leftLastItem || leftFoundStartData == leftLastItem; } bool recurseRight = false; if (rightSearchStart < ( int )ownEnd) recurseRight = true; if (recurseLeft && recurseRight) { //Push the right branch onto the stack, and work in the left one boundStack.append(qMakePair(qMakePair(( uint )rightSearchStart, ownEnd), rightNode)); } if (recurseLeft) { currentNode = leftNode; if (leftSearchLast != -1) ownEnd = leftSearchLast + 1; } else if (recurseRight) { currentNode = rightNode; ownStart = rightSearchStart; } else { goto boundsUp; } } } } ///Returns the first valid index that has an extracted data-value larger or equal to @p data. ///Returns -1 if nothing is found. int lowerBound(const Data2& data, int start, int end) { int currentBound = -1; while (1) { if (start >= end) return currentBound; int center = (start + end) / 2; //Skip free items, since they cannot be used for ordering for (; center < end;) { if (!Handler::isFree(this->m_data[center])) break; ++center; } if (center == end) { end = (start + end) / 2; //No non-free items found in second half, so continue search in the other } else { Data2 centerData = KeyExtractor::extract(this->m_data[center]); //Even if the data equals we must continue searching to the left, since there may be multiple matching if (data == centerData || data < centerData) { currentBound = center; end = (start + end) / 2; } else { //Continue search in second half start = center + 1; } } } } //Bounds that yet need to be matched. Always a range in the own vector, and a node that all items in the range are contained in KDevVarLengthArray, typename TreeSet::Node>> boundStack; TreeSet m_rhs; int m_match = -1, m_matchBound; Data2 m_matchingTo; bool m_noFiltering; }; ///Same as above, except that it visits all filtered items with a visitor, instead of iterating over them. ///This is more efficient. The visiting is done directly from within the constructor. template class ConvenientEmbeddedSetTreeFilterVisitor : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetTreeFilterVisitor() { } - typedef QPair, typename TreeSet::Node> Bounds; + using Bounds = QPair, typename TreeSet::Node>; struct Bound { inline Bound(uint s, uint e, const typename TreeSet::Node& n) : start(s) , end(e) , node(n) { } Bound() { } uint start; uint end; typename TreeSet::Node node; }; ///@param noFiltering whether the given input is pre-filtered. If this is true, base will be iterated without skipping any items. ConvenientEmbeddedSetTreeFilterVisitor(Visitor& visitor, const ConvenientEmbeddedSetIterator& base, const TreeSet& rhs, bool noFiltering = false) : ConvenientEmbeddedSetIterator(base) , m_visitor(visitor) , m_rhs(rhs) , m_noFiltering(noFiltering) { if (m_noFiltering) { for (uint a = 0; a < this->m_dataSize; ++a) visitor(this->m_data[a]); return; } if (rhs.node().isValid()) { //Correctly initialize the initial bounds int ownStart = lowerBound(rhs.node().firstItem(), 0, this->m_dataSize); if (ownStart == -1) return; int ownEnd = lowerBound(rhs.node().lastItem(), ownStart, this->m_dataSize); if (ownEnd == -1) ownEnd = this->m_dataSize; else ownEnd += 1; go(Bound(( uint )ownStart, ( uint )ownEnd, rhs.node())); } } private: void go(Bound bound) { KDevVarLengthArray bounds; while (true) { if (bound.start >= bound.end) goto nextBound; if (bound.node.isFinalNode()) { //Check whether the item is contained int b = lowerBound(*bound.node, bound.start, bound.end); if (b != -1) { const Data2& matchTo(*bound.node); if (KeyExtractor::extract(this->m_data[b]) == matchTo) { while (1) { m_visitor(this->m_data[b]); b = this->firstValidItem(b + 1, this->m_dataSize); if (b < ( int )this->m_dataSize && b != -1 && KeyExtractor::extract(this->m_data[b]) == matchTo) continue; else break; } } } goto nextBound; } else { //This is not a final node, split up the search into the sub-nodes typename TreeSet::Node leftNode = bound.node.leftChild(); typename TreeSet::Node rightNode = bound.node.rightChild(); Q_ASSERT(leftNode.isValid()); Q_ASSERT(rightNode.isValid()); Data2 leftLastItem = leftNode.lastItem(); int rightSearchStart = lowerBound(rightNode.firstItem(), bound.start, bound.end); if (rightSearchStart == -1) rightSearchStart = bound.end; int leftSearchLast = lowerBound(leftLastItem, bound.start, rightSearchStart != -1 ? rightSearchStart : bound.end); if (leftSearchLast == -1) leftSearchLast = rightSearchStart - 1; bool recurseLeft = false; if (leftSearchLast > ( int )bound.start) { recurseLeft = true; //There must be something in the range bound.start -> leftSearchLast that matches the range } else if (( int )bound.start == leftSearchLast) { //Check if the one item item under leftSearchStart is contained in the range Data2 leftFoundStartData = KeyExtractor::extract(this->m_data[bound.start]); recurseLeft = leftFoundStartData < leftLastItem || leftFoundStartData == leftLastItem; } bool recurseRight = false; if (rightSearchStart < ( int )bound.end) recurseRight = true; if (recurseLeft && recurseRight) bounds.append(Bound(rightSearchStart, bound.end, rightNode)); if (recurseLeft) { bound.node = leftNode; if (leftSearchLast != -1) bound.end = leftSearchLast + 1; } else if (recurseRight) { bound.node = rightNode; bound.start = rightSearchStart; } else { goto nextBound; } continue; } nextBound: if (bounds.isEmpty()) { return; } else { bound = bounds.back(); bounds.pop_back(); } } } ///Returns the first valid index that has an extracted data-value larger or equal to @p data. ///Returns -1 if nothing is found. int lowerBound(const Data2& data, int start, int end) { int currentBound = -1; while (1) { if (start >= end) return currentBound; int center = (start + end) / 2; //Skip free items, since they cannot be used for ordering for (; center < end;) { if (!Handler::isFree(this->m_data[center])) break; ++center; } if (center == end) { end = (start + end) / 2; //No non-free items found in second half, so continue search in the other } else { Data2 centerData = KeyExtractor::extract(this->m_data[center]); //Even if the data equals we must continue searching to the left, since there may be multiple matching if (data == centerData || data < centerData) { currentBound = center; end = (start + end) / 2; } else { //Continue search in second half start = center + 1; } } } } //Bounds that yet need to be matched. Always a range in the own vector, and a node that all items in the range are contained in Visitor& m_visitor; TreeSet m_rhs; bool m_noFiltering; }; template ConvenientEmbeddedSetIterator ConstantConvenientEmbeddedSet::iterator() const { return ConvenientEmbeddedSetIterator(m_data, m_dataSize, m_centralFreeItem); } ///This is a simple set implementation based on the embedded free tree algorithms. ///The core advantage of the whole thing is that the wole set is represented by a consecutive ///memory-area, and thus can be stored or copied using a simple memcpy. ///However in many cases it's better using the algorithms directly in such cases. /// ///However even for normal tasks this implementation does have some advantages over std::set: ///- Many times faster iteration through contained data ///- Lower memory-usage if the objects are small, since there is no heap allocation overhead ///- Can be combined with other embedded-free-list based sets using algorithms in ConstantConvenientEmbeddedSet ///Disadvantages: ///- Significantly slower insertion template class ConvenientFreeListSet { public: - typedef ConvenientEmbeddedSetIterator Iterator; + using Iterator = ConvenientEmbeddedSetIterator; ConvenientFreeListSet() { } ///Re-construct a set from its components ConvenientFreeListSet(int centralFreeItem, QVector data) : m_data(data) , m_centralFree(centralFreeItem) { } ///You can use this to store the set to disk and later give it together with data() to the constructor, thus reconstructing it. int centralFreeItem() const { return m_centralFree; } const QVector& data() const { return m_data; } void insert(const Data& item) { if (contains(item)) return; KDevelop::EmbeddedTreeAddItem add(m_data.data(), m_data.size(), m_centralFree, item); if (( int )add.newItemCount() != ( int )m_data.size()) { QVector newData; newData.resize(add.newItemCount()); add.transferData(newData.data(), newData.size()); m_data = newData; } } Iterator iterator() const { return Iterator(m_data.data(), m_data.size(), m_centralFree); } bool contains(const Data& item) const { KDevelop::EmbeddedTreeAlgorithms alg(m_data.data(), m_data.size(), m_centralFree); return alg.indexOf(Data(item)) != -1; } void remove(const Data& item) { KDevelop::EmbeddedTreeRemoveItem remove(m_data.data(), m_data.size(), m_centralFree, item); if (( int )remove.newItemCount() != ( int )m_data.size()) { QVector newData; newData.resize(remove.newItemCount()); remove.transferData(newData.data(), newData.size()); m_data = newData; } } private: int m_centralFree = -1; QVector m_data; }; } #endif diff --git a/kdevplatform/util/path.h b/kdevplatform/util/path.h index f7c6b69530..2c986103c5 100644 --- a/kdevplatform/util/path.h +++ b/kdevplatform/util/path.h @@ -1,383 +1,383 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KDEVELOP_PATH_H #define KDEVELOP_PATH_H #include "utilexport.h" #include #include #include #include namespace KDevelop { /** * @return Return a string representation of @p url, if possible as local file * * Convenience method for working around https://bugreports.qt.io/browse/QTBUG-41729 */ QString KDEVPLATFORMUTIL_EXPORT toUrlOrLocalFile(const QUrl& url, QUrl::FormattingOptions options = QUrl::FormattingOptions( QUrl::PrettyDecoded )); /** * @brief Path data type optimized for memory consumption. * * This class holds data that represents a local or remote path. * In the project model e.g. we usually store whole trees such as * * /foo/ * /foo/bar/ * /foo/bar/asdf.txt * * Normal QString/QUrl/QUrl types would not share any memory for these paths * at all. This class though can share the segments of the paths and thus * consume far less total memory. * * Just like the URL types, the Path can point to a remote location. * * Example for how to leverage memory sharing for the above input data: * * @code * Path foo("/foo"); * Path bar(foo, "bar"); * Path asdf(foo, "asdf.txt"); * @endcode * * @note Just as with QString e.g. you won't share any data implicitly when * you do something like this: * * @code * Path foo1("/foo"); * Path foo2("/foo"); * @endcode */ class KDEVPLATFORMUTIL_EXPORT Path { public: - typedef QVector List; + using List = QVector; /** * Construct an empty, invalid Path. */ Path(); /** * Create a Path out of a string representation of a path or URL. * * @note Not every kind of remote URL is supported, rather only path-like * URLs without fragments, queries, sub-Paths and the like are supported. * * Empty paths or URLs containing one of the following are considered invalid: * - URL fragments (i.e. "...#fragment") * - URL queries (i.e. "...?query=") * - sub-URLs (i.e. "file:///tmp/kde.tgz#gzip:/#tar:/kdevelop") * * @sa isValid() */ explicit Path(const QString& pathOrUrl); /** * Convert a QUrl to a Path. * * @note Not every kind of remote URL is supported, rather only path-like * URLs without fragments, queries, sub-Paths and the like are supported. * * Empty paths or URLs containing one of the following are considered invalid: * - URL fragments (i.e. "...#fragment") * - URL queries (i.e. "...?query=") * - sub-URLs (i.e. "file:///tmp/kde.tgz#gzip:/#tar:/kdevelop") * * @sa isValid() */ explicit Path(const QUrl& url); /** * Create a copy of @p base and optionally append a path segment @p subPath. * * This implicitly shares the data of @p base and thus is very efficient * memory wise compared to creating two Paths from separate strings. * * @p subPath A relative or absolute path. If this is an absolute path then * the path in @p base will be ignored and only the remote data copied. If * this is a relative path it will be combined with @p base. * * @sa addPath() */ Path(const Path& base, const QString& subPath = QString()); /** * Equality comparison between @p other and this Path. * * @return true if @p other is equal to this Path. */ inline bool operator==(const Path& other) const { return m_data == other.m_data; } /** * Inequality comparison between @p other and this Path. * * @return true if @p other is different from this Path. */ inline bool operator!=(const Path& other) const { return !operator==(other); } /** * Less-than path comparison between @p other and this Path. * * @return true if this Path is less than @p other. */ bool operator<(const Path& other) const; /** * Greater-than path comparison between @p other and this Path. * * @return true if this Path is greater than @p other. */ inline bool operator>(const Path& other) const { return other < *this; } /** * Less-than-equal path comparison between @p other and this Path. * * @return true if this Path is less than @p other or equal. */ inline bool operator<=(const Path& other) const { return *this < other || other == *this; } /** * Greater-than-equal path comparison between @p other and this Path. * * @return true if this Path is greater than @p other or equal. */ inline bool operator>=(const Path& other) const { return other < *this || other == *this; } /** * Check whether this Path is valid. * * @return true if the Path is valid, i.e. contains data, false otherwise. */ inline bool isValid() const { return !m_data.isEmpty(); } /** * Check whether this Path is empty. * * @return true if the Path is empty, false otherwise, i.e. if it contains data. */ inline bool isEmpty() const { return m_data.isEmpty(); } /** * Convert the Path to a string, yielding either the plain path for local * paths or the stringified URL for remote Paths. * * @return a string representation of this Path. * @sa path() */ QString pathOrUrl() const; /** * Return the path segment of this Path. This is the same for local paths * as calling pathOrUrl(). The difference is only for remote paths which * would return the protocol, host etc. data in pathOrUrl but not here. * * @return the path segment of this Path. * * @sa pathOrUrl() */ QString path() const; /** * Return the path for local path and an empty string for remote paths. */ QString toLocalFile() const; /** * @return the relative path from this path to @p path. * * Examples: * @code * Path p1("/foo/bar"); * Path p2("/foo/bar/asdf/test.txt"); * p1.relativePath(p2); // returns: asdf/test.txt * Path p3("/foo/asdf/lala"); * p3.relativePath(p1); // returns: ../../bar * @endcode * * @sa QUrl::relativePath */ QString relativePath(const Path& path) const; /** * @return True if this path is the parent of @p path. * * For instance, ftp://host/dir/ is a parent of ftp://host/dir/subdir/blub, * or /foo is a parent of /foo/bar. * * NOTE: Contrary to QUrl::isParentOf this returns false if the path equals this one. */ bool isParentOf(const Path& path) const; /** * @return True if this path is the direct parent of @p path. * * For instance, ftp://host/dir/ is the direct parent of ftp://host/dir/subdir, * but ftp.//host/ is a parent but not the direct parent. * * This is more efficient than @code path.parent() == *this @endcode since * it does not require any temporary allocations as for the parent() call. */ bool isDirectParentOf(const Path& path) const; /** * @return the prefix of a remote URL containing protocol, host, port etc. pp. * If this path is not remote, this returns an empty string. */ QString remotePrefix() const; /** * @return an implicitly shared copy of the internal data. */ inline QVector segments() const { return m_data; } /** * @return the Path converted to a QUrl. */ QUrl toUrl() const; /** * @return true when this Path points to a local file, false otherwise. */ bool isLocalFile() const; /** * @return true when this Path points to a remote file, false otherwise. */ bool isRemote() const; /** * @return the last element of the path. * * This will never return the remote URL prefix. */ QString lastPathSegment() const; /** * Set the file name of this Path, i.e. the last element of the path. * * This will never overwrite the remote URL prefix. */ void setLastPathSegment(const QString& name); /** * Append @p path to this Path. * * NOTE: If @p path starts with a slash, this function ignores it. * I.e. you cannot set the path this way. @sa QUrl::addPath */ void addPath(const QString& path); /** * @return the path pointing to the parent folder of this path. * * @sa KIO::upUrl() */ Path parent() const; /** * @return true when this path has a parent and false if this is a root or invalid path. */ bool hasParent() const; /** * Clear the path, i.e. make it invalid and empty. */ void clear(); /** * Change directory by relative path @p dir. * * NOTE: This is expensive. * * @sa QUrl::cd */ Path cd(const QString& dir) const; private: // for remote urls the first element contains the a Path prefix // containing the protocol, user, port etc. pp. QVector m_data; }; KDEVPLATFORMUTIL_EXPORT uint qHash(const Path& path); /** * Convert the @p list of QUrls to a list of Paths. */ KDEVPLATFORMUTIL_EXPORT Path::List toPathList(const QList& list); /** * Convert the @p list of QStrings to a list of Paths. */ KDEVPLATFORMUTIL_EXPORT Path::List toPathList(const QList& list); } /** * qDebug() stream operator. Writes the string to the debug output. */ KDEVPLATFORMUTIL_EXPORT QDebug operator<<(QDebug s, const KDevelop::Path& string); namespace QTest { template char* toString(const T&); /** * QTestLib integration to have nice output in e.g. QCOMPARE failures. */ template<> KDEVPLATFORMUTIL_EXPORT char* toString(const KDevelop::Path& path); } Q_DECLARE_TYPEINFO(KDevelop::Path, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::Path) Q_DECLARE_TYPEINFO(KDevelop::Path::List, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::Path::List) #endif // KDEVELOP_PATH_H diff --git a/kdevplatform/util/tests/test_embeddedfreetree.cpp b/kdevplatform/util/tests/test_embeddedfreetree.cpp index 92068739c5..44084bcb08 100644 --- a/kdevplatform/util/tests/test_embeddedfreetree.cpp +++ b/kdevplatform/util/tests/test_embeddedfreetree.cpp @@ -1,677 +1,677 @@ /* This file is part of KDevelop 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. */ unsigned int extractor_div_with = 0; #include #include #include #include #include #include #include #include #include #include #include #include struct TestItem { explicit TestItem(uint _value = 0) : value(_value) { } uint value; int leftChild = -1; int rightChild = -1; bool operator==(const TestItem& rhs) const { return value == rhs.value; } bool operator<(const TestItem& item) const { return value < item.value; } }; struct TestItemConversion { static uint toIndex(const TestItem& item) { return item.value; } static TestItem toItem(uint index) { return TestItem(index); } }; struct Extractor { static TestItem extract(const TestItem& item) { return TestItem(item.value / extractor_div_with); } }; clock_t std_insertion = 0, std_removal = 0, std_contains = 0, std_iteration = 0, emb_insertion = 0, emb_removal = 0, emb_contains = 0, emb_iteration = 0; QString toString(const std::set& set) { QString ret; for (auto it = set.begin(); it != set.end(); ++it) ret += QStringLiteral("%1 ").arg(*it); return ret; } bool operator==(const std::set& a, const std::set& b) { if (a.size() != b.size()) { qDebug() << "size mismatch" << toString(a) << ": " << toString(b); return false; } auto aIt = a.begin(); auto bIt = b.begin(); for (; aIt != a.end(); ++aIt, ++bIt) if (*aIt != *bIt) { qDebug() << "mismatch" << toString(a) << ": " << toString(b); return false; } return true; } struct TestItemHandler { public: static int rightChild(const TestItem& m_data) { return m_data.rightChild; } static int leftChild(const TestItem& m_data) { return m_data.leftChild; } static void setLeftChild(TestItem& m_data, int child) { m_data.leftChild = child; } static void setRightChild(TestItem& m_data, int child) { m_data.rightChild = child; } //Copies this item into the given one static void copyTo(const TestItem& m_data, TestItem& data) { data = m_data; } static void createFreeItem(TestItem& data) { data = TestItem(); } static inline bool isFree(const TestItem& m_data) { return !m_data.value; } static const TestItem& data(const TestItem& m_data) { return m_data; } inline static bool equals(const TestItem& m_data, const TestItem& rhs) { return m_data.value == rhs.value; } }; class TestItemBasedSet { public: TestItemBasedSet() { } void insert(uint i) { TestItem item(i); KDevelop::EmbeddedTreeAddItem add(data.data(), data.size(), m_centralFree, item); if (( int )add.newItemCount() != ( int )data.size()) { QVector newData; newData.resize(add.newItemCount()); add.transferData(newData.data(), newData.size()); data = newData; } } bool contains(uint item) { KDevelop::EmbeddedTreeAlgorithms alg(data.data(), data.size(), m_centralFree); return alg.indexOf(TestItem(item)) != -1; } void remove(uint i) { TestItem item(i); KDevelop::EmbeddedTreeRemoveItem remove(data.data(), data.size(), m_centralFree, item); if (( int )remove.newItemCount() != ( int )data.size()) { QVector newData; newData.resize(remove.newItemCount()); remove.transferData(newData.data(), newData.size()); data = newData; } } std::set toSet() const { std::set ret; for (int a = 0; a < data.size(); ++a) if (data[a].value) ret.insert(data[a].value); return ret; } void verify() { //1. verify order uint last = 0; uint freeCount = 0; for (int a = 0; a < data.size(); ++a) { if (data[a].value) { QVERIFY(last < data[a].value); last = data[a].value; } else { ++freeCount; } } KDevelop::EmbeddedTreeAlgorithms algorithms(data.data(), data.size(), m_centralFree); uint countFree = algorithms.countFreeItems(); QCOMPARE(freeCount, countFree); algorithms.verifyTreeConsistent(); } uint getItem(uint number) const { Q_ASSERT(number < ( uint )data.size()); uint ret = 0; uint size = ( uint )data.size(); uint current = 0; for (uint a = 0; a < size; ++a) { if (data[a].value) { //Only count the non-free items if (current == number) ret = data[a].value; ++current; } else { //This is a free item } } return ret; } private: int m_centralFree = -1; QVector data; }; class TestSet { public: void add(uint i) { if (realSet.find(i) != realSet.end()) { QVERIFY(set.contains(i)); return; } else { QVERIFY(!set.contains(i)); } clock_t start = clock(); realSet.insert(i); std_insertion += clock() - start; start = clock(); set.insert(i); emb_insertion += clock() - start; start = clock(); bool contained = realSet.find(i) != realSet.end(); std_contains += clock() - start; start = clock(); set.contains(i); emb_contains += clock() - start; QVERIFY(set.contains(i)); QVERIFY(contained); set.verify(); } void remove(uint i) { if (realSet.find(i) != realSet.end()) { QVERIFY(set.contains(i)); } else { QVERIFY(!set.contains(i)); return; } clock_t start = clock(); set.remove(i); emb_removal += clock() - start; start = clock(); realSet.erase(i); std_removal += clock() - start; QVERIFY(!set.contains(i)); } uint size() const { return realSet.size(); } uint getItem(uint number) const { Q_ASSERT(number < size()); uint current = 0; uint ret = 0; clock_t start = clock(); for (auto it = realSet.begin(); it != realSet.end(); ++it) { if (current == number) { ret = *it; } ++current; } std_iteration += clock() - start; start = clock(); set.getItem(number); emb_iteration += clock() - start; Q_ASSERT(ret); return ret; } void verify() { QVERIFY(realSet == set.toSet()); set.verify(); } private: std::set realSet; TestItemBasedSet set; }; float toSeconds(clock_t time) { return (( float )time) / CLOCKS_PER_SEC; } struct StaticRepository { static Utils::BasicSetRepository* repository() { static Utils::BasicSetRepository repository(QStringLiteral("test repository")); return &repository; } }; struct UintSetVisitor { std::set& s; explicit UintSetVisitor(std::set& _s) : s(_s) { } inline bool operator()(const TestItem& item) { s.insert(item.value); return true; } }; struct NothingDoVisitor { inline bool operator()(const TestItem& item) { Q_UNUSED(item); return true; } }; class TestEmbeddedFreeTree : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); } void cleanupTestCase() { KDevelop::TestCore::shutdown(); } void randomizedTest() { const int cycles = 10000; const int valueRange = 1000; const int removeProbability = 40; //Percent TestSet set; srand(time(nullptr)); for (int a = 0; a < cycles; ++a) { if (a % (cycles / 10) == 0) { qDebug() << "cycle" << a; } bool remove = (rand() % 100) < removeProbability; if (remove && set.size()) { set.remove(set.getItem(rand() % set.size())); } else { int value = (rand() % valueRange) + 1; set.add(value); } set.verify(); } qDebug() << "Performance embedded list: insertion:" << toSeconds(emb_insertion) << "removal:" << toSeconds( emb_removal) << "contains:" << toSeconds(emb_contains) << "iteration:" << toSeconds(emb_iteration); qDebug() << "Performance std::set: insertion:" << toSeconds(std_insertion) << "removal:" << toSeconds( std_removal) << "contains:" << toSeconds(std_contains) << "iteration:" << toSeconds(std_iteration); } void sequentialTest() { TestSet set; set.add(5); set.verify(); set.remove(5); set.verify(); set.add(3); set.verify(); set.add(4); set.verify(); set.add(7); set.verify(); set.remove(3); set.verify(); set.remove(7); set.verify(); set.add(6); set.verify(); set.add(1); set.verify(); set.add(9); set.verify(); set.remove(4); set.verify(); set.remove(9); set.verify(); set.add(1); set.verify(); set.add(2); set.verify(); set.add(3); set.verify(); set.add(4); set.verify(); set.add(5); set.verify(); set.remove(1); set.verify(); set.remove(3); set.verify(); set.add(15); set.verify(); set.add(16); set.verify(); set.add(17); set.verify(); set.add(18); set.verify(); set.remove(18); set.verify(); set.remove(17); set.verify(); set.add(9); set.verify(); } void testFiltering() { clock_t stdTime = 0; clock_t algoTime = 0; clock_t treeAlgoTime = 0; clock_t treeAlgoVisitorTime = 0; clock_t insertionStdTime = 0; clock_t insertionAlgoTime = 0; clock_t insertionTreeAlgoTime = 0; - typedef Utils::StorableSet RepositorySet; + using RepositorySet = Utils::StorableSet; const uint cycles = 3000; const uint setSize = 1500; uint totalItems = 0, totalFilteredItems = 0; srand(time(nullptr)); for (uint a = 0; a < cycles; ++a) { KDevelop::ConvenientFreeListSet set1; std::set testSet1; KDevelop::ConvenientFreeListSet set2; std::set testSet2; RepositorySet repSet2; if (a % (cycles / 10) == 0) { qDebug() << "cycle" << a; } //Build the sets extractor_div_with = (rand() % 10) + 1; for (uint a = 0; a < setSize; ++a) { uint value = rand() % 3000; uint divValue = value / extractor_div_with; if (!divValue) continue; // qDebug() << "inserting" << value; auto it = testSet1.lower_bound(value); int pos = set1.iterator().lowerBound(TestItem(value)); //This tests the upperBound functionality if (pos != -1) { QVERIFY(it != testSet1.end()); QVERIFY(set1.data()[pos].value == *it); } else { QVERIFY(it == testSet1.end()); } if ((rand() % 10) == 0) { set1.insert(TestItem(value)); testSet1.insert(value); } //This is tuned so in the end, about 99% of all declarations are filtered out, like in the symbol table. if ((rand() % (extractor_div_with * 100)) == 0) { clock_t start = clock(); set2.insert(TestItem(divValue)); insertionStdTime += clock() - start; start = clock(); testSet2.insert(divValue); insertionAlgoTime += clock() - start; start = clock(); repSet2.insert(TestItem(divValue)); insertionTreeAlgoTime += clock() - start; start = clock(); } } std::set verifySet1; for (KDevelop::ConvenientFreeListSet::Iterator it = set1.iterator(); it; ++it) verifySet1.insert(it->value); std::set verifySet2; for (KDevelop::ConvenientFreeListSet::Iterator it = set2.iterator(); it; ++it) verifySet2.insert(it->value); std::set verifyRepSet2; for (RepositorySet::Iterator it = repSet2.iterator(); it; ++it) verifyRepSet2.insert((*it).value); QCOMPARE(verifySet1, testSet1); QCOMPARE(verifySet2, testSet2); QCOMPARE(verifyRepSet2, testSet2); std::set algoFiltered; std::set treeAlgoFiltered; std::set treeAlgoVisitorFiltered; { //Do the filtering once without actions on the filtered items, just for calculating the time clock_t start = clock(); { KDevelop::ConvenientEmbeddedSetFilterIterator filterIterator(set1.iterator(), set2.iterator()); while (filterIterator) ++filterIterator; algoTime += clock() - start; } start = clock(); { KDevelop::ConvenientEmbeddedSetTreeFilterIterator filterIterator(set1.iterator(), repSet2); while (filterIterator) ++filterIterator; treeAlgoTime += clock() - start; } { start = clock(); NothingDoVisitor v; KDevelop::ConvenientEmbeddedSetTreeFilterVisitor visit(v, set1.iterator(), repSet2); treeAlgoVisitorTime += clock() - start; } start = clock(); for (auto it = testSet1.begin(); it != testSet1.end(); ++it) { if (testSet2.count((*it) / extractor_div_with) == 1) { } } stdTime += clock() - start; } { KDevelop::ConvenientEmbeddedSetFilterIterator filterIterator(set1.iterator(), set2.iterator()); while (filterIterator) { algoFiltered.insert(filterIterator->value); ++filterIterator; } } { KDevelop::ConvenientEmbeddedSetTreeFilterIterator filterIterator(set1.iterator(), repSet2); while (filterIterator) { treeAlgoFiltered.insert((*filterIterator).value); ++filterIterator; } } { UintSetVisitor v(treeAlgoVisitorFiltered); KDevelop::ConvenientEmbeddedSetTreeFilterVisitor visit(v, set1.iterator(), repSet2); } totalItems += testSet1.size(); totalFilteredItems += algoFiltered.size(); std::set stdFiltered; for (auto it = testSet1.begin(); it != testSet1.end(); ++it) { if (testSet2.count((*it) / extractor_div_with) == 1) { stdFiltered.insert(*it); } } QCOMPARE(algoFiltered, stdFiltered); QCOMPARE(treeAlgoFiltered, stdFiltered); QCOMPARE(treeAlgoVisitorFiltered, stdFiltered); } qDebug() << "Filtering performance: embedded-list filtering:" << toSeconds(algoTime) << "set-repository filtering:" << toSeconds(treeAlgoTime) << "set-repository visitor filtering:" << toSeconds( treeAlgoVisitorTime) << "std::set filtering:" << toSeconds(stdTime) << "Normal -> Embedded speedup ratio:" << (toSeconds(stdTime) / toSeconds(algoTime)) << "Normal -> Repository speedup ratio:" << (toSeconds(stdTime) / toSeconds(treeAlgoVisitorTime)) << "total processed items:" << totalItems << "total items after filtering:" << totalFilteredItems; qDebug() << "Insertion: embedded-list:" << toSeconds(insertionAlgoTime) << "set-repository:" << toSeconds( insertionTreeAlgoTime) << "std::set:" << toSeconds(insertionStdTime); } }; #include "test_embeddedfreetree.moc" QTEST_MAIN(TestEmbeddedFreeTree) diff --git a/kdevplatform/vcs/interfaces/ipatchsource.h b/kdevplatform/vcs/interfaces/ipatchsource.h index e9b8f104c9..135095ac1e 100644 --- a/kdevplatform/vcs/interfaces/ipatchsource.h +++ b/kdevplatform/vcs/interfaces/ipatchsource.h @@ -1,113 +1,113 @@ /* Copyright 2006 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 KDEVPLATFORM_IPATCHSOURCE_H #define KDEVPLATFORM_IPATCHSOURCE_H #include #include #include #include #include namespace KDevelop { ///Any entity may delete an IPatchSource based object at will, so it must always be referenced through a QPointer (Just use IPatchSource::Ptr). class KDEVPLATFORMVCS_EXPORT IPatchSource : public QObject { Q_OBJECT public: - typedef QPointer Ptr; + using Ptr = QPointer; ///Name of the patch, that will be shown in a combo box. Should ///describe the patch in a useful way, for example "Difference to base in kdevplatform/language" virtual QString name() const = 0; ///Icon that will be shown with the patch virtual QIcon icon() const; ///Should tell if the patch is already applied on the local version. virtual bool isAlreadyApplied() const = 0; ///Explicit updating of the patch: If it is a dynamic patch, it ///should re-compare the files or whatever needs to be done ///If the patch has changed, patchChanged needs to be emitted virtual void update() = 0; ///Name of the patch file virtual QUrl file() const = 0; ///Should return the base-dir of the patch virtual QUrl baseDir() const = 0; ///Can return a custom widget that should be shown to the user with this patch ///The ownership of the widget is shared between the caller and the patch-source (both may delete it at will) ///The default implementation returns zero virtual QWidget* customWidget() const; ///May return a custom text for the "Finish Review" action. ///The default implementation returns QString(), which means that the default is used virtual QString finishReviewCustomText() const; ///Called when the user has reviewed and accepted this patch ///If canSelectFiles() returned true, @p selection will contain the list of selected files ///If this returns false, the review is not finished. virtual bool finishReview(const QList& selection); ///Called when the user has rejected this patch virtual void cancelReview(); ///Should return whether the user may cancel this review (cancelReview will be called when he does) ///The default implementation returns false virtual bool canCancel() const; ///Should return whether the user should be able to select files of the patch ///The files available for selection will be all files affected by the patch, and the files ///return by additionalSelectableFiles() The default implementation returns false virtual bool canSelectFiles() const; ///May return an additional list of selectable files together with short description strings for this patch ///The default implementation returns an empty list virtual QMap additionalSelectableFiles() const; /// Depth - number of directories to left-strip from paths in the patch - see "patch -p" /// Defaults to 0 virtual uint depth() const; Q_SIGNALS: ///Should be emitted whenever the patch has changed. void patchChanged(); }; class KDEVPLATFORMVCS_EXPORT IPatchReview { public: virtual ~IPatchReview(); enum ReviewMode { OpenAndRaise ///< Opens the related files in the review area, switches to that area, and raises the patch-review tool view }; ///Starts a review on the patch: Opens the patch and the files within the review area virtual void startReview(IPatchSource* patch, ReviewMode mode = OpenAndRaise) = 0; }; } Q_DECLARE_INTERFACE(KDevelop::IPatchReview, "org.kdevelop.IPatchReview") #endif // KDEVPLATFORM_IPATCHSOURCE_H diff --git a/plugins/clang/codecompletion/context.cpp b/plugins/clang/codecompletion/context.cpp index 16f2e55e53..ad8bb4bf81 100644 --- a/plugins/clang/codecompletion/context.cpp +++ b/plugins/clang/codecompletion/context.cpp @@ -1,1325 +1,1325 @@ /* * This file is part of KDevelop * Copyright 2014 Milian Wolff * Copyright 2015 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "context.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../util/clangdebug.h" #include "../util/clangtypes.h" #include "../util/clangutils.h" #include "../duchain/clangdiagnosticevaluator.h" #include "../duchain/parsesession.h" #include "../duchain/duchainutils.h" #include "../duchain/navigationwidget.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include #include using namespace KDevelop; namespace { /// Maximum return-type string length in completion items const int MAX_RETURN_TYPE_STRING_LENGTH = 20; /// Priority of code-completion results. NOTE: Keep in sync with Clang code base. enum CodeCompletionPriority { /// Priority for the next initialization in a constructor initializer list. CCP_NextInitializer = 7, /// Priority for an enumeration constant inside a switch whose condition is of the enumeration type. CCP_EnumInCase = 7, CCP_LocalDeclarationMatch = 8, CCP_DeclarationMatch = 12, CCP_LocalDeclarationSimiliar = 17, /// Priority for a send-to-super completion. CCP_SuperCompletion = 20, CCP_DeclarationSimiliar = 25, /// Priority for a declaration that is in the local scope. CCP_LocalDeclaration = 34, /// Priority for a member declaration found from the current method or member function. CCP_MemberDeclaration = 35, /// Priority for a language keyword (that isn't any of the other categories). CCP_Keyword = 40, /// Priority for a code pattern. CCP_CodePattern = 40, /// Priority for a non-type declaration. CCP_Declaration = 50, /// Priority for a type. CCP_Type = CCP_Declaration, /// Priority for a constant value (e.g., enumerator). CCP_Constant = 65, /// Priority for a preprocessor macro. CCP_Macro = 70, /// Priority for a nested-name-specifier. CCP_NestedNameSpecifier = 75, /// Priority for a result that isn't likely to be what the user wants, but is included for completeness. CCP_Unlikely = 80 }; /** * Common base class for Clang code completion items. */ template class CompletionItem : public Base { public: CompletionItem(const QString& display, const QString& prefix) : Base() , m_display(display) , m_prefix(prefix) , m_unimportant(false) { } ~CompletionItem() override = default; QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* /*model*/) const override { if (role == Qt::DisplayRole) { if (index.column() == CodeCompletionModel::Prefix) { return m_prefix; } else if (index.column() == CodeCompletionModel::Name) { return m_display; } } return {}; } void markAsUnimportant() { m_unimportant = true; } protected: QString m_display; QString m_prefix; bool m_unimportant; }; class OverrideItem : public CompletionItem { public: OverrideItem(const QString& nameAndParams, const QString& returnType) : CompletionItem( nameAndParams, i18n("Override %1", returnType) ) , m_returnType(returnType) { } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == Qt::DecorationRole) { if (index.column() == KTextEditor::CodeCompletionModel::Icon) { static const QIcon icon = QIcon::fromTheme(QStringLiteral("CTparents")); return icon; } } return CompletionItem::data(index, role, model); } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { view->document()->replaceText(word, m_returnType + QLatin1Char(' ') + m_display.replace(QRegularExpression(QStringLiteral("\\s*=\\s*0")), QString()) + QLatin1String(" override;")); } private: QString m_returnType; }; /** * Specialized completion item class for items which are represented by a Declaration */ class DeclarationItem : public CompletionItem { public: DeclarationItem(Declaration* dec, const QString& display, const QString& prefix, const QString& replacement) : CompletionItem(display, prefix) , m_replacement(replacement) { m_declaration = dec; } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == CodeCompletionModel::MatchQuality && m_matchQuality) { return m_matchQuality; } auto ret = CompletionItem::data(index, role, model); if (ret.isValid()) { return ret; } return NormalDeclarationCompletionItem::data(index, role, model); } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { QString repl = m_replacement; DUChainReadLocker lock; if(!m_declaration){ return; } if(m_declaration->isFunctionDeclaration()) { const auto functionType = m_declaration->type(); // protect against buggy code that created the m_declaration, // to mark it as a function but not assign a function type if (!functionType) return; auto doc = view->document(); // Function pointer? bool funcptr = false; const auto line = doc->line(word.start().line()); auto pos = word.end().column() - 1; while ( pos > 0 && (line.at(pos).isLetterOrNumber() || line.at(pos) == QLatin1Char(':')) ) { pos--; if ( line.at(pos) == QLatin1Char('&') ) { funcptr = true; break; } } auto restEmpty = doc->characterAt(word.end() + KTextEditor::Cursor{0, 1}) == QChar(); bool didAddParentheses = false; if ( !funcptr && doc->characterAt(word.end()) != QLatin1Char('(') ) { repl += QLatin1String("()"); didAddParentheses = true; } view->document()->replaceText(word, repl); if (functionType->indexedArgumentsSize() && didAddParentheses) { view->setCursorPosition(word.start() + KTextEditor::Cursor(0, repl.size() - 1)); } auto returnTypeIntegral = functionType->returnType().cast(); if ( restEmpty && !funcptr && returnTypeIntegral && returnTypeIntegral->dataType() == IntegralType::TypeVoid ) { // function returns void and rest of line is empty -- nothing can be done with the result if (functionType->indexedArgumentsSize() ) { // we placed the cursor inside the () view->document()->insertText(view->cursorPosition() + KTextEditor::Cursor(0, 1), QStringLiteral(";")); } else { // we placed the cursor after the () view->document()->insertText(view->cursorPosition(), QStringLiteral(";")); view->setCursorPosition(view->cursorPosition() + KTextEditor::Cursor{0, 1}); } } } else { view->document()->replaceText(word, repl); } } bool createsExpandingWidget() const override { return true; } QWidget* createExpandingWidget(const CodeCompletionModel* /*model*/) const override { return new ClangNavigationWidget(m_declaration, AbstractNavigationWidget::EmbeddableWidget); } int matchQuality() const { return m_matchQuality; } ///Sets match quality from 0 to 10. 10 is the best fit. void setMatchQuality(int value) { m_matchQuality = value; } void setInheritanceDepth(int depth) { m_inheritanceDepth = depth; } int argumentHintDepth() const override { return m_depth; } void setArgumentHintDepth(int depth) { m_depth = depth; } protected: int m_matchQuality = 0; int m_depth = 0; QString m_replacement; }; class ImplementsItem : public DeclarationItem { public: static QString replacement(const FuncImplementInfo& info) { QString replacement = info.templatePrefix; if (!info.isDestructor && !info.isConstructor) { replacement += info.returnType + QLatin1Char(' '); } replacement += info.prototype + QLatin1String("\n{\n}\n"); return replacement; } explicit ImplementsItem(const FuncImplementInfo& item) : DeclarationItem(item.declaration.data(), item.prototype, i18n("Implement %1", item.isConstructor ? QStringLiteral("") : item.isDestructor ? QStringLiteral("") : item.returnType), replacement(item) ) { } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (index.column() == CodeCompletionModel::Arguments) { // our display string already contains the arguments return {}; } return DeclarationItem::data(index, role, model); } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { auto* const document = view->document(); // try and replace leading typed text that match the proposed implementation const QString leading = document->line(word.end().line()).left(word.end().column()); const QString leadingNoSpace = removeWhitespace(leading); if (!leadingNoSpace.isEmpty() && (removeWhitespace(m_display).startsWith(leadingNoSpace) || removeWhitespace(m_replacement).startsWith(leadingNoSpace))) { const int removeSize = leading.end() - std::find_if_not(leading.begin(), leading.end(), [](QChar c){ return c.isSpace(); }); const KTextEditor::Cursor newStart = {word.end().line(), word.end().column() - removeSize}; document->replaceText({newStart, word.end()}, m_replacement); } else { document->replaceText(word, m_replacement); } // place cursor after the opening brace view->setCursorPosition(view->cursorPosition() + KTextEditor::Cursor{-2, 1}); } }; class ArgumentHintItem : public DeclarationItem { public: struct CurrentArgumentRange { int start; int end; }; ArgumentHintItem(Declaration* decl, const QString& prefix, const QString& name, const QString& arguments, const CurrentArgumentRange& range) : DeclarationItem(decl, name, prefix, {}) , m_range(range) , m_arguments(arguments) {} QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == CodeCompletionModel::CustomHighlight && index.column() == CodeCompletionModel::Arguments && argumentHintDepth()) { QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); const QList highlighting { QVariant(m_range.start), QVariant(m_range.end), boldFormat, }; return highlighting; } if (role == CodeCompletionModel::HighlightingMethod && index.column() == CodeCompletionModel::Arguments && argumentHintDepth()) { return QVariant(CodeCompletionModel::CustomHighlighting); } if (index.column() == CodeCompletionModel::Arguments) { return m_arguments; } return DeclarationItem::data(index, role, model); } private: CurrentArgumentRange m_range; QString m_arguments; }; /** * A minimalistic completion item for macros and such */ class SimpleItem : public CompletionItem { public: SimpleItem(const QString& display, const QString& prefix, const QString& replacement, const QIcon& icon = QIcon()) : CompletionItem(display, prefix) , m_replacement(replacement) , m_icon(icon) { } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { view->document()->replaceText(word, m_replacement); } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Icon) { return m_icon; } if (role == CodeCompletionModel::UnimportantItemRole) { return m_unimportant; } return CompletionItem::data(index, role, model); } private: QString m_replacement; QIcon m_icon; }; /** * Return true in case position @p position represents a cursor inside a comment */ bool isInsideComment(CXTranslationUnit unit, CXFile file, const KTextEditor::Cursor& position) { if (!position.isValid()) { return false; } // TODO: This may get very slow for a large TU, investigate if we can improve this function auto begin = clang_getLocation(unit, file, 1, 1); auto end = clang_getLocation(unit, file, position.line() + 1, position.column() + 1); CXSourceRange range = clang_getRange(begin, end); // tokenize the whole range from the start until 'position' // if we detect a comment token at this position, return true const ClangTokens tokens(unit, range); for (CXToken token : tokens) { CXTokenKind tokenKind = clang_getTokenKind(token); if (tokenKind != CXToken_Comment) { continue; } auto range = ClangRange(clang_getTokenExtent(unit, token)); if (range.toRange().contains(position)) { return true; } } return false; } QString& elideStringRight(QString& str, int length) { if (str.size() > length + 3) { return str.replace(length, str.size() - length, QStringLiteral("...")); } return str; } /** * @return Value suited for @ref CodeCompletionModel::MatchQuality in the range [0.0, 10.0] (the higher the better) * * See http://clang.llvm.org/doxygen/CodeCompleteConsumer_8h_source.html for list of priorities * They (currently) have a range from [-3, 80] (the lower, the better) */ int codeCompletionPriorityToMatchQuality(unsigned int completionPriority) { return 10u - qBound(0u, completionPriority, 80u) / 8; } int adjustPriorityForType(const AbstractType::Ptr& type, int completionPriority) { const auto modifier = 4; if (type) { const auto whichType = type->whichType(); if (whichType == AbstractType::TypePointer || whichType == AbstractType::TypeReference) { // Clang considers all pointers as similar, this is not what we want. completionPriority += modifier; } else if (whichType == AbstractType::TypeStructure) { // Clang considers all classes as similar too... completionPriority += modifier; } else if (whichType == AbstractType::TypeDelayed) { completionPriority += modifier; } else if (whichType == AbstractType::TypeAlias) { auto aliasedType = type.cast(); return adjustPriorityForType(aliasedType ? aliasedType->type() : AbstractType::Ptr(), completionPriority); } else if (whichType == AbstractType::TypeFunction) { auto functionType = type.cast(); return adjustPriorityForType(functionType ? functionType->returnType() : AbstractType::Ptr(), completionPriority); } } else { completionPriority += modifier; } return completionPriority; } /// Adjusts priority for the @p decl int adjustPriorityForDeclaration(Declaration* decl, unsigned int completionPriority) { if(completionPriority < CCP_LocalDeclarationSimiliar || completionPriority > CCP_SuperCompletion){ return completionPriority; } return adjustPriorityForType(decl->abstractType(), completionPriority); } /** * @return Whether the declaration represented by identifier @p identifier qualifies as completion result * * For example, we don't want to offer SomeClass::SomeClass as completion item to the user * (otherwise we'd end up generating code such as 's.SomeClass();') */ bool isValidCompletionIdentifier(const QualifiedIdentifier& identifier) { const int count = identifier.count(); if (identifier.count() < 2) { return true; } const Identifier scope = identifier.at(count-2); const Identifier id = identifier.last(); if (scope == id) { return false; // is constructor } const QString idString = id.toString(); if (idString.startsWith(QLatin1Char('~')) && scope.toString() == idString.midRef(1)) { return false; // is destructor } return true; } /** * @return Whether the declaration represented by identifier @p identifier qualifies as "special" completion result * * "Special" completion results are items that are likely not regularly used. * * Examples: * - 'SomeClass::operator=(const SomeClass&)' */ bool isValidSpecialCompletionIdentifier(const QualifiedIdentifier& identifier) { if (identifier.count() < 2) { return false; } const Identifier id = identifier.last(); const QString idString = id.toString(); if (idString.startsWith(QLatin1String("operator="))) { return true; // is assignment operator } return false; } Declaration* findDeclaration(const QualifiedIdentifier& qid, const DUContextPointer& ctx, const CursorInRevision& position, QSet& handled) { PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().declarations(qid); const auto top = ctx->topContext(); const auto& importedContexts = top->importedParentContexts(); for (auto it = decl.iterator(); it; ++it) { // if the context is not included, then this match is not correct for our consideration // this fixes issues where we used to include matches from files that did not have // anything to do with the current TU, e.g. the main from a different file or stuff like that // it also reduces the chance of us picking up a function of the same name from somewhere else // also, this makes sure the context has the correct language and we don't get confused by stuff // from other language plugins if (std::none_of(importedContexts.begin(), importedContexts.end(), [it] (const DUContext::Import& import) { return import.topContextIndex() == it->indexedTopContext().index(); })) { continue; } auto declaration = it->declaration(); if (!declaration) { // Mitigate problems such as: Cannot load a top-context from file "/home/kfunk/.cache/kdevduchain/kdevelop-{foo}/topcontexts/6085" // - the required language-support for handling ID 55 is probably not loaded qCWarning(KDEV_CLANG) << "Detected an invalid declaration for" << qid; continue; } if (declaration->kind() == Declaration::Instance && !declaration->isFunctionDeclaration()) { break; } if (!handled.contains(declaration)) { handled.insert(declaration); return declaration; } } const auto foundDeclarations = ctx->findDeclarations(qid, position); for (auto dec : foundDeclarations) { if (!handled.contains(dec)) { handled.insert(dec); return dec; } } return nullptr; } /// If any parent of this context is a class, the closest class declaration is returned, nullptr otherwise Declaration* classDeclarationForContext(const DUContextPointer& context, const CursorInRevision& position) { auto parent = context; while (parent) { if (parent->type() == DUContext::Class) { break; } if (auto owner = parent->owner()) { // Work-around for out-of-line methods. They have Helper context instead of Class context if (owner->context() && owner->context()->type() == DUContext::Helper) { auto qid = owner->qualifiedIdentifier(); qid.pop(); QSet tmp; auto decl = findDeclaration(qid, context, position, tmp); if (decl && decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { parent = decl->internalContext(); break; } } } parent = parent->parentContext(); } return parent ? parent->owner() : nullptr; } class LookAheadItemMatcher { public: explicit LookAheadItemMatcher(const TopDUContextPointer& ctx) : m_topContext(ctx) , m_enabled(ClangSettingsManager::self()->codeCompletionSettings().lookAhead) {} /// Adds all local declarations for @p declaration into possible look-ahead items. void addDeclarations(Declaration* declaration) { if (!m_enabled) { return; } if (declaration->kind() != Declaration::Instance) { return; } auto type = typeForDeclaration(declaration); auto identifiedType = dynamic_cast(type.data()); if (!identifiedType) { return; } addDeclarationsForType(identifiedType, declaration); } /// Add type for matching. This type'll be used for filtering look-ahead items /// Only items with @p type will be returned through @sa matchedItems void addMatchedType(const IndexedType& type) { matchedTypes.insert(type); } /// @return look-ahead items that math given types. @sa addMatchedType QList matchedItems() { QList lookAheadItems; for (const auto& pair: qAsConst(possibleLookAheadDeclarations)) { auto decl = pair.first; if (matchedTypes.contains(decl->indexedType())) { auto parent = pair.second; const QString access = parent->abstractType()->whichType() == AbstractType::TypePointer ? QStringLiteral("->") : QStringLiteral("."); const QString text = parent->identifier().toString() + access + decl->identifier().toString(); auto item = new DeclarationItem(decl, text, {}, text); item->setMatchQuality(8); lookAheadItems.append(CompletionTreeItemPointer(item)); } } return lookAheadItems; } private: AbstractType::Ptr typeForDeclaration(const Declaration* decl) { return TypeUtils::targetType(decl->abstractType(), m_topContext.data()); } void addDeclarationsForType(const IdentifiedType* identifiedType, Declaration* declaration) { if (auto typeDecl = identifiedType->declaration(m_topContext.data())) { if (dynamic_cast(typeDecl->logicalDeclaration(m_topContext.data()))) { if (!typeDecl->internalContext()) { return; } const auto& localDeclarations = typeDecl->internalContext()->localDeclarations(); for (auto localDecl : localDeclarations) { if(localDecl->identifier().isEmpty()){ continue; } if(auto classMember = dynamic_cast(localDecl)){ // TODO: Also add protected/private members if completion is inside this class context. if(classMember->accessPolicy() != Declaration::Public){ continue; } } if(!declaration->abstractType()){ continue; } if (declaration->abstractType()->whichType() == AbstractType::TypeIntegral) { if (auto integralType = declaration->abstractType().cast()) { if (integralType->dataType() == IntegralType::TypeVoid) { continue; } } } possibleLookAheadDeclarations.insert({localDecl, declaration}); } } } } // Declaration and it's context - typedef QPair DeclarationContext; + using DeclarationContext = QPair; /// Types of declarations that look-ahead completion items can have QSet matchedTypes; // List of declarations that can be added to the Look Ahead group // Second declaration represents context QSet possibleLookAheadDeclarations; TopDUContextPointer m_topContext; bool m_enabled; }; struct MemberAccessReplacer : public QObject { Q_OBJECT public: enum Type { None, DotToArrow, ArrowToDot }; public Q_SLOTS: void replaceCurrentAccess(MemberAccessReplacer::Type type) { if (auto document = ICore::self()->documentController()->activeDocument()) { if (auto textDocument = document->textDocument()) { auto activeView = document->activeTextView(); if (!activeView) { return; } auto cursor = activeView->cursorPosition(); QString oldAccess, newAccess; if (type == ArrowToDot) { oldAccess = QStringLiteral("->"); newAccess = QStringLiteral("."); } else { oldAccess = QStringLiteral("."); newAccess = QStringLiteral("->"); } auto oldRange = KTextEditor::Range(cursor - KTextEditor::Cursor(0, oldAccess.length()), cursor); // This code needed for testReplaceMemberAccess test // Maybe we should do a similar thing for '->' to '.' direction, but this is not so important while (textDocument->text(oldRange) == QLatin1String(" ") && oldRange.start().column() >= 0) { oldRange = KTextEditor::Range({oldRange.start().line(), oldRange.start().column() - 1}, {oldRange.end().line(), oldRange.end().column() - 1}); } if (oldRange.start().column() >= 0 && textDocument->text(oldRange) == oldAccess) { textDocument->replaceText(oldRange, newAccess); } } } } }; static MemberAccessReplacer s_memberAccessReplacer; } Q_DECLARE_METATYPE(MemberAccessReplacer::Type) ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& context, const ParseSessionData::Ptr& sessionData, const QUrl& url, const KTextEditor::Cursor& position, const QString& text, const QString& followingText ) : CodeCompletionContext(context, text + followingText, CursorInRevision::castFromSimpleCursor(position), 0) , m_results(nullptr, clang_disposeCodeCompleteResults) , m_parseSessionData(sessionData) { qRegisterMetaType(); const QByteArray file = url.toLocalFile().toUtf8(); ParseSession session(m_parseSessionData); QVector otherUnsavedFiles; { ForegroundLock lock; otherUnsavedFiles = ClangUtils::unsavedFiles(); } QVector allUnsaved; { const unsigned int completeOptions = clang_defaultCodeCompleteOptions(); CXUnsavedFile unsaved; unsaved.Filename = file.constData(); const QByteArray content = m_text.toUtf8(); unsaved.Contents = content.constData(); unsaved.Length = content.size(); allUnsaved.reserve(otherUnsavedFiles.size() + 1); for (const auto& f : qAsConst(otherUnsavedFiles)) { allUnsaved.append(f.toClangApi()); } allUnsaved.append(unsaved); m_results.reset(clang_codeCompleteAt(session.unit(), file.constData(), position.line() + 1, position.column() + 1, allUnsaved.data(), allUnsaved.size(), completeOptions)); if (!m_results) { qCWarning(KDEV_CLANG) << "Something went wrong during 'clang_codeCompleteAt' for file" << file; return; } auto numDiagnostics = clang_codeCompleteGetNumDiagnostics(m_results.get()); for (uint i = 0; i < numDiagnostics; i++) { auto diagnostic = clang_codeCompleteGetDiagnostic(m_results.get(), i); auto diagnosticType = ClangDiagnosticEvaluator::diagnosticType(diagnostic); clang_disposeDiagnostic(diagnostic); if (diagnosticType == ClangDiagnosticEvaluator::ReplaceWithArrowProblem || diagnosticType == ClangDiagnosticEvaluator::ReplaceWithDotProblem) { MemberAccessReplacer::Type replacementType; if (diagnosticType == ClangDiagnosticEvaluator::ReplaceWithDotProblem) { replacementType = MemberAccessReplacer::ArrowToDot; } else { replacementType = MemberAccessReplacer::DotToArrow; } QMetaObject::invokeMethod(&s_memberAccessReplacer, "replaceCurrentAccess", Qt::QueuedConnection, Q_ARG(MemberAccessReplacer::Type, replacementType)); m_valid = false; return; } } auto addMacros = ClangSettingsManager::self()->codeCompletionSettings().macros; if (!addMacros) { m_filters |= NoMacros; } } if (!m_results->NumResults) { const auto trimmedText = text.trimmed(); if (trimmedText.endsWith(QLatin1Char('.'))) { // TODO: This shouldn't be needed if Clang provided diagnostic. // But it doesn't always do it, so let's try to manually determine whether '.' is used instead of '->' m_text = trimmedText.leftRef(trimmedText.size() - 1) + QStringLiteral("->"); CXUnsavedFile unsaved; unsaved.Filename = file.constData(); const QByteArray content = m_text.toUtf8(); unsaved.Contents = content.constData(); unsaved.Length = content.size(); allUnsaved[allUnsaved.size() - 1] = unsaved; m_results.reset(clang_codeCompleteAt(session.unit(), file.constData(), position.line() + 1, position.column() + 1 + 1, allUnsaved.data(), allUnsaved.size(), clang_defaultCodeCompleteOptions())); if (m_results && m_results->NumResults) { QMetaObject::invokeMethod(&s_memberAccessReplacer, "replaceCurrentAccess", Qt::QueuedConnection, Q_ARG(MemberAccessReplacer::Type, MemberAccessReplacer::DotToArrow)); } m_valid = false; return; } } // check 'isValidPosition' after parsing the new content auto clangFile = session.file(file); if (!isValidPosition(session.unit(), clangFile)) { m_valid = false; return; } m_completionHelper.computeCompletions(session, clangFile, position); } ClangCodeCompletionContext::~ClangCodeCompletionContext() { } bool ClangCodeCompletionContext::isValidPosition(CXTranslationUnit unit, CXFile file) const { if (isInsideComment(unit, file, m_position.castToSimpleCursor())) { clangDebug() << "Invalid completion context: Inside comment"; return false; } return true; } QList ClangCodeCompletionContext::completionItems(bool& abort, bool /*fullCompletion*/) { if (!m_valid || !m_duContext || !m_results) { return {}; } const auto ctx = DUContextPointer(m_duContext->findContextAt(m_position)); /// Normal completion items, such as 'void Foo::foo()' QList items; /// Stuff like 'Foo& Foo::operator=(const Foo&)', etc. Not regularly used by our users. QList specialItems; /// Macros from the current context QList macros; /// Builtins reported by Clang QList builtin; // two sets of handled declarations to prevent duplicates and make sure we show // all available overloads QSet handled; // this is only used for the CXCursor_OverloadCandidate completion items QSet overloadsHandled; LookAheadItemMatcher lookAheadMatcher(TopDUContextPointer(ctx->topContext())); // If ctx is/inside the Class context, this represents that context. const auto currentClassContext = classDeclarationForContext(ctx, m_position); clangDebug() << "Clang found" << m_results->NumResults << "completion results"; for (uint i = 0; i < m_results->NumResults; ++i) { if (abort) { return {}; } auto result = m_results->Results[i]; #if CINDEX_VERSION_MINOR >= 30 const bool isOverloadCandidate = result.CursorKind == CXCursor_OverloadCandidate; #else const bool isOverloadCandidate = false; #endif const auto availability = clang_getCompletionAvailability(result.CompletionString); if (availability == CXAvailability_NotAvailable) { continue; } const bool isMacroDefinition = result.CursorKind == CXCursor_MacroDefinition; if (isMacroDefinition && m_filters & NoMacros) { continue; } const bool isBuiltin = (result.CursorKind == CXCursor_NotImplemented); if (isBuiltin && m_filters & NoBuiltins) { continue; } const bool isDeclaration = !isMacroDefinition && !isBuiltin; if (isDeclaration && m_filters & NoDeclarations) { continue; } if (availability == CXAvailability_NotAccessible && (!isDeclaration || !currentClassContext)) { continue; } // the string that would be needed to type, usually the identifier of something. Also we use it as name for code completion declaration items. QString typed; // the return type of a function e.g. QString resultType; // the replacement text when an item gets executed QString replacement; QString arguments; ArgumentHintItem::CurrentArgumentRange argumentRange; //BEGIN function signature parsing // nesting depth of parentheses int parenDepth = 0; enum FunctionSignatureState { // not yet inside the function signature Before, // any token is part of the function signature now Inside, // finished parsing the function signature After }; // current state FunctionSignatureState signatureState = Before; //END function signature parsing std::function processChunks = [&] (CXCompletionString completionString) { const uint chunks = clang_getNumCompletionChunks(completionString); for (uint j = 0; j < chunks; ++j) { const auto kind = clang_getCompletionChunkKind(completionString, j); if (kind == CXCompletionChunk_Optional) { completionString = clang_getCompletionChunkCompletionString(completionString, j); if (completionString) { processChunks(completionString); } continue; } // We don't need function signature for declaration items, we can get it directly from the declaration. Also adding the function signature to the "display" would break the "Detailed completion" option. if (isDeclaration && !typed.isEmpty()) { // TODO: When parent context for CXCursor_OverloadCandidate is fixed remove this check if (!isOverloadCandidate) { break; } } const QString string = ClangString(clang_getCompletionChunkText(completionString, j)).toString(); switch (kind) { case CXCompletionChunk_TypedText: typed = string; replacement += string; break; case CXCompletionChunk_ResultType: resultType = string; continue; case CXCompletionChunk_Placeholder: if (signatureState == Inside) { arguments += string; } continue; case CXCompletionChunk_LeftParen: if (signatureState == Before && !parenDepth) { signatureState = Inside; } parenDepth++; break; case CXCompletionChunk_RightParen: --parenDepth; if (signatureState == Inside && !parenDepth) { arguments += QLatin1Char(')'); signatureState = After; } break; case CXCompletionChunk_Text: if (isOverloadCandidate) { typed += string; } else if (result.CursorKind == CXCursor_EnumConstantDecl) { replacement += string; } else if (result.CursorKind == CXCursor_EnumConstantDecl) { replacement += string; } break; case CXCompletionChunk_CurrentParameter: argumentRange.start = arguments.size(); argumentRange.end = string.size(); break; default: break; } if (signatureState == Inside) { arguments += string; } } }; processChunks(result.CompletionString); // TODO: No closing paren if default parameters present if (isOverloadCandidate && !arguments.endsWith(QLatin1Char(')'))) { arguments += QLatin1Char(')'); } // ellide text to the right for overly long result types (templates especially) elideStringRight(resultType, MAX_RETURN_TYPE_STRING_LENGTH); static const auto noIcon = QIcon(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/pics/namespace.png"))); if (isDeclaration) { const Identifier id(typed); QualifiedIdentifier qid; ClangString parent(clang_getCompletionParent(result.CompletionString, nullptr)); if (parent.c_str() != nullptr) { qid = QualifiedIdentifier(parent.toString()); } qid.push(id); if (!isValidCompletionIdentifier(qid)) { continue; } if (isOverloadCandidate && resultType.isEmpty() && parent.isEmpty()) { // workaround: find constructor calls for non-namespaced classes // TODO: return the namespaced class as parent in libclang qid.push(id); } auto found = findDeclaration(qid, ctx, m_position, isOverloadCandidate ? overloadsHandled : handled); CompletionTreeItemPointer item; if (found) { // TODO: Bug in Clang: protected members from base classes not accessible in derived classes. if (availability == CXAvailability_NotAccessible) { if (auto cl = dynamic_cast(found)) { if (cl->accessPolicy() != Declaration::Protected) { continue; } auto declarationClassContext = classDeclarationForContext(DUContextPointer(found->context()), m_position); uint steps = 10; auto inheriters = DUChainUtils::inheriters(declarationClassContext, steps); if(!inheriters.contains(currentClassContext)){ continue; } } else { continue; } } DeclarationItem* declarationItem = nullptr; if (isOverloadCandidate) { declarationItem = new ArgumentHintItem(found, resultType, typed, arguments, argumentRange); declarationItem->setArgumentHintDepth(1); } else { declarationItem = new DeclarationItem(found, typed, resultType, replacement); } const unsigned int completionPriority = adjustPriorityForDeclaration(found, clang_getCompletionPriority(result.CompletionString)); const bool bestMatch = completionPriority <= CCP_SuperCompletion; //don't set best match property for internal identifiers, also prefer declarations from current file const auto isInternal = found->indexedIdentifier().identifier().toString().startsWith(QLatin1String("__")); if (bestMatch && !isInternal ) { const int matchQuality = codeCompletionPriorityToMatchQuality(completionPriority); declarationItem->setMatchQuality(matchQuality); // TODO: LibClang missing API to determine expected code completion type. lookAheadMatcher.addMatchedType(found->indexedType()); } else { declarationItem->setInheritanceDepth(completionPriority); lookAheadMatcher.addDeclarations(found); } if ( isInternal ) { declarationItem->markAsUnimportant(); } item = declarationItem; } else { if (isOverloadCandidate) { // TODO: No parent context for CXCursor_OverloadCandidate items, hence qid is broken -> no declaration found auto ahi = new ArgumentHintItem({}, resultType, typed, arguments, argumentRange); ahi->setArgumentHintDepth(1); item = ahi; } else { // still, let's trust that Clang found something useful and put it into the completion result list clangDebug() << "Could not find declaration for" << qid; auto instance = new SimpleItem(typed + arguments, resultType, replacement, noIcon); instance->markAsUnimportant(); item = CompletionTreeItemPointer(instance); } } if (isValidSpecialCompletionIdentifier(qid)) { // If it's a special completion identifier e.g. "operator=(const&)" and we don't have a declaration for it, don't add it into completion list, as this item is completely useless and pollutes the test case. // This happens e.g. for "class A{}; a.|". At | we have "operator=(const A&)" as a special completion identifier without a declaration. if(item->declaration()){ specialItems.append(item); } } else { items.append(item); } continue; } if (result.CursorKind == CXCursor_MacroDefinition) { // TODO: grouping of macros and built-in stuff const auto text = QString(typed + arguments); auto instance = new SimpleItem(text, resultType, replacement, noIcon); auto item = CompletionTreeItemPointer(instance); if ( text.startsWith(QLatin1Char('_')) ) { instance->markAsUnimportant(); } macros.append(item); } else if (result.CursorKind == CXCursor_NotImplemented) { auto instance = new SimpleItem(typed, resultType, replacement, noIcon); auto item = CompletionTreeItemPointer(instance); builtin.append(item); } } if (abort) { return {}; } addImplementationHelperItems(); addOverwritableItems(); eventuallyAddGroup(i18n("Special"), 700, specialItems); eventuallyAddGroup(i18n("Look-ahead Matches"), 800, lookAheadMatcher.matchedItems()); eventuallyAddGroup(i18n("Builtin"), 900, builtin); eventuallyAddGroup(i18n("Macros"), 1000, macros); return items; } void ClangCodeCompletionContext::eventuallyAddGroup(const QString& name, int priority, const QList& items) { if (items.isEmpty()) { return; } auto* node = new CompletionCustomGroupNode(name, priority); node->appendChildren(items); m_ungrouped << CompletionTreeElementPointer(node); } void ClangCodeCompletionContext::addOverwritableItems() { auto overrideList = m_completionHelper.overrides(); if (overrideList.isEmpty()) { return; } QList overrides; QList overridesAbstract; for (const auto& info : overrideList) { QStringList params; params.reserve(info.params.size()); for (const auto& param : info.params) { params << param.type + QLatin1Char(' ') + param.id; } QString nameAndParams = info.name + QLatin1Char('(') + params.join(QStringLiteral(", ")) + QLatin1Char(')'); if(info.isConst) nameAndParams = nameAndParams + QLatin1String(" const"); if(info.isPureVirtual) nameAndParams = nameAndParams + QLatin1String(" = 0"); auto item = CompletionTreeItemPointer(new OverrideItem(nameAndParams, info.returnType)); if (info.isPureVirtual) overridesAbstract << item; else overrides << item; } eventuallyAddGroup(i18n("Abstract Override"), 0, overridesAbstract); eventuallyAddGroup(i18n("Virtual Override"), 0, overrides); } void ClangCodeCompletionContext::addImplementationHelperItems() { const auto implementsList = m_completionHelper.implements(); if (implementsList.isEmpty()) { return; } QList implements; implements.reserve(implementsList.size()); for (const auto& info : implementsList) { implements << CompletionTreeItemPointer(new ImplementsItem(info)); } eventuallyAddGroup(i18n("Implement Function"), 0, implements); } QList ClangCodeCompletionContext::ungroupedElements() { return m_ungrouped; } ClangCodeCompletionContext::ContextFilters ClangCodeCompletionContext::filters() const { return m_filters; } void ClangCodeCompletionContext::setFilters(const ClangCodeCompletionContext::ContextFilters& filters) { m_filters = filters; } #include "context.moc" diff --git a/plugins/clang/codegen/adaptsignatureaction.h b/plugins/clang/codegen/adaptsignatureaction.h index 4d93b22e04..6d3891b35b 100644 --- a/plugins/clang/codegen/adaptsignatureaction.h +++ b/plugins/clang/codegen/adaptsignatureaction.h @@ -1,68 +1,68 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk 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 ADAPTSIGNATUREACTION_H #define ADAPTSIGNATUREACTION_H #include #include #include namespace KDevelop { class RenameAction; } -typedef QPair ParameterItem; +using ParameterItem = QPair; struct Signature { Signature(){}; QVector parameters; QList defaultParams; KDevelop::IndexedType returnType; bool isConst; }; class AdaptSignatureAction : public KDevelop::IAssistantAction { Q_OBJECT public: AdaptSignatureAction(const KDevelop::DeclarationId& definitionId, const KDevelop::ReferencedTopDUContext& definitionContext, const Signature& oldSignature, const Signature& newSignature, bool editingDefinition, const QList& renameActions); ~AdaptSignatureAction() override; QString description() const override; QString toolTip() const override; void execute() override; private: KDevelop::DeclarationId m_otherSideId; KDevelop::ReferencedTopDUContext m_otherSideTopContext; Signature m_oldSignature; Signature m_newSignature; bool m_editingDefinition; QList m_renameActions; }; #endif // ADAPTSIGNATUREACTION_H diff --git a/plugins/clang/duchain/builder.cpp b/plugins/clang/duchain/builder.cpp index 12cf6139fa..8c1c663ce7 100644 --- a/plugins/clang/duchain/builder.cpp +++ b/plugins/clang/duchain/builder.cpp @@ -1,1593 +1,1593 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 Milian Wolff * * 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 "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) using namespace KDevelop; namespace { #if CINDEX_VERSION_MINOR >= 100 // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } #endif //BEGIN helpers // HACK: current alias type template machinery is badly broken wrt spelling // location, work around this by adjusting all references to point to child // type alias node with proper location // TODO: investigate upstream implementation of CXCursor_TypeAliasTemplateDecl CXCursor findEmbeddedTypeAlias(CXCursor aliasTemplate) { auto result = clang_getNullCursor(); clang_visitChildren(aliasTemplate, [] (CXCursor cursor, CXCursor, CXClientData data) { if (clang_getCursorKind(cursor) == CXCursor_TypeAliasDecl) { auto res = reinterpret_cast(data); *res = cursor; return CXChildVisit_Break; } return CXChildVisit_Continue; }, &result); return result; } /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); // HACK: see notes at getEmbeddedTypeAlias() if (clang_getCursorKind(referenced) == CXCursor_TypeAliasTemplateDecl) { return findEmbeddedTypeAlias(referenced); } if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { if (CursorKindTraits::isClassTemplate(cursor.kind)) { // TODO: how to handle functions here? We don't want to add the "real" function arguments here // and there does not seem to be an API to get the template arguments for non-specializations easily // NOTE: using the QString overload of the Identifier ctor here, so that the template name gets parsed return Identifier(ClangString(clang_getCursorDisplayName(cursor)).toString()); } return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } #if CINDEX_VERSION_MINOR >= 100 // FIXME https://bugs.llvm.org/show_bug.cgi?id=35333 QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(jsonTestRun())) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } #endif AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { - typedef StructureType Type; + using Type = StructureType; }; template struct IdType::type> { - typedef TypeAliasType Type; + using Type = TypeAliasType; }; template struct IdType::type> { - typedef TypeAliasType Type; + using Type = TypeAliasType; }; template struct IdType::type> { - typedef EnumerationType Type; + using Type = EnumerationType; }; template struct IdType::type> { - typedef EnumeratorType Type; + using Type = EnumeratorType; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { - typedef Declaration Type; + using Type = Declaration; }; template struct DeclType::type> { - typedef MacroDefinition Type; + using Type = MacroDefinition; }; template struct DeclType::type> { - typedef ForwardDeclaration Type; + using Type = ForwardDeclaration; }; template struct DeclType::type> { - typedef ClassDeclaration Type; + using Type = ClassDeclaration; }; template struct DeclType::type> { - typedef ClassFunctionDeclaration Type; + using Type = ClassFunctionDeclaration; }; template struct DeclType::type> { - typedef FunctionDeclaration Type; + using Type = FunctionDeclaration; }; template struct DeclType::type> { - typedef FunctionDefinition Type; + using Type = FunctionDefinition; }; template struct DeclType::type> { - typedef NamespaceAliasDeclaration Type; + using Type = NamespaceAliasDeclaration; }; template struct DeclType::type> { - typedef ClassMemberDeclaration Type; + using Type = ClassMemberDeclaration; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context, const QSet& keepAliveContexts) : context(context) , keepAliveContexts(keepAliveContexts) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; foreach (auto childContext, previousChildContexts) { if (!keepAliveContexts.contains(childContext)) { delete childContext; } } qDeleteAll(previousChildDeclarations); if (resortChildContexts) { context->resortChildContexts(); } if (resortLocalDeclarations) { context->resortLocalDeclarations(); } } DUContext* context; // when updating, this contains child contexts of the current parent context QVector previousChildContexts; // when updating, this contains contexts that must not be deleted QSet keepAliveContexts; // when updating, this contains child declarations of the current parent context QVector previousChildDeclarations; bool resortChildContexts = false; bool resortLocalDeclarations = false; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); CXChildVisitResult dispatchTypeAliasTemplate(CXCursor cursor, CXCursor parent) { return CursorKindTraits::isClass(clang_getCursorKind(parent)) ? buildTypeAliasTemplateDecl(cursor) : buildTypeAliasTemplateDecl(cursor); } template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); template CXChildVisitResult buildTypeAliasTemplateDecl(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); template CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); m_parentContext->resortLocalDeclarations = true; setDeclData(cursor, decl); m_cursorToDeclarationCache[cursor] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); #if CINDEX_VERSION_MINOR >= 32 decl->setExplicitlyTyped(clang_getCursorType(cursor).kind != CXType_Auto); #endif m_cursorToDeclarationCache[cursor] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->resortChildContexts = true; m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod) { CXCursor semParent = clang_getCursorSemanticParent(cursor); // only import the semantic parent if it differs from the lexical parent if (!clang_Cursor_isNull(semParent) && !clang_equalCursors(semParent, clang_getCursorLexicalParent(cursor))) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; if (decl) { t->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user t->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString())))); } return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); // TODO: We should really expose more types to libclang! if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } #if CINDEX_VERSION_MINOR >= 32 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto deducedType = clang_getCanonicalType(type); bool isDeduced = deducedType.kind != CXType_Invalid && deducedType.kind != CXType_Auto; return !isDeduced ? createDelayedType(type) : makeType(deducedType, parent); } #endif #if CINDEX_VERSION_MINOR >= 34 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto underyingType = clang_Type_getNamedType(type); return makeType(underyingType, parent); } #endif /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer& declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; if (decl) { cst->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user Identifier id(tStr); id.clearTemplateIdentifiers(); cst->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id)))); } return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; // always build a context for class templates and functions, otherwise we "leak" // the function/template parameter declarations into the surrounding context, // which can lead to interesting bugs, like https://bugs.kde.org/show_bug.cgi?id=368067 constexpr bool hasContext = isDefinition || CursorKindTraits::isFunction(CK) || CursorKindTraits::isClassTemplate(CK); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) #if CINDEX_VERSION_MINOR < 100 // FIXME https://bugs.llvm.org/show_bug.cgi?id=35333 decl->setComment(KDevelop::formatComment(ClangString(clang_Cursor_getRawCommentText(cursor)).toByteArray())); #else decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); #endif if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 32 decl->setMutable(clang_CXXField_isMutable(cursor)); #endif #if CINDEX_VERSION_MINOR >= 30 auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0) decl->setSizeOf(sizeOf); if (offset >= 0) decl->setBitOffsetOf(offset); if (alignOf >= 0) decl->setAlignOf(alignOf); } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } #if CINDEX_VERSION_MINOR >= 30 auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0) decl->setSizeOf(sizeOf); if (alignOf >= 0) decl->setAlignOf(alignOf); #endif } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); for (const QString& defaultArg : defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setIsAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); // TODO: Set flags in one go? (needs new API in kdevplatform) const auto attributes = ClangUtils::specialAttributes(cursor); decl->setIsSignal(attributes & FunctionSignalFlag); decl->setIsSlot(attributes & FunctionSlotFlag); decl->setIsFinal(attributes & FinalFunctionFlag); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) -> CXChildVisitResult { if (clang_getCursorKind(cursor) == CXCursor_NamespaceRef) { const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; } else { return visitCursor(cursor, parent, data); } }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context, m_parentContext->keepAliveContexts)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } template CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { if (CK == CXCursor_LambdaExpr || m_parentContext->context->type() == DUContext::Function) { auto context = createContext(cursor); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } template CXChildVisitResult Visitor::buildTypeAliasTemplateDecl(CXCursor cursor) { auto aliasDecl = findEmbeddedTypeAlias(cursor); // NOTE: using aliasDecl here averts having to add a workaround to makeId() auto id = makeId(aliasDecl); // create template context to prevent leaking child template params auto context = createContext(cursor, QualifiedIdentifier(id)); using DeclType = typename DeclType::Type; createDeclaration(aliasDecl, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) { // NOTE: immediately recurse into embedded alias decl return clang_getCursorKind(cursor) == CXCursor_TypeAliasDecl ? CXChildVisit_Recurse : visitCursor(cursor, parent, data); }, this); return CXChildVisit_Continue; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto it = m_cursorToDeclarationCache.constFind(cursor); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursor, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_BlockPointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionNoProto); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); #if CINDEX_VERSION_MINOR >= 32 UseKind(CXType_Auto); #endif #if CINDEX_VERSION_MINOR >= 34 UseKind(CXType_Elaborated); #endif #if CINDEX_VERSION_MINOR >= 38 UseKind(CXType_Float128); #endif UseKind(CXType_Complex); case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } #undef UseKind } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); auto top = includes[file]; // when updating, this contains child contexts that should be kept alive // even when they are not part of the AST anymore // this is required for some assistants, such as the signature assistant QSet keepAliveContexts; { DUChainReadLocker lock; foreach (const auto& problem, top->problems()) { const auto& desc = problem->description(); if (desc.startsWith(QLatin1String("Return type of out-of-line definition of '")) && desc.endsWith(QLatin1String("' differs from that in the declaration"))) { auto ctx = top->findContextAt(problem->range().start); // keep the context and its parents alive // this also keeps declarations in this context alive while (ctx) { keepAliveContexts << ctx; ctx = ctx->parentContext(); } } } } CurrentContext parent(top, keepAliveContexts); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = referencedCursor(cursor); if (clang_Cursor_isNull(referenced)) { continue; } // first, try the canonical referenced cursor // this is important to get the correct function declaration e.g. auto canonicalReferenced = clang_getCanonicalCursor(referenced); auto used = findDeclaration(canonicalReferenced); if (!used) { // if the above failed, try the non-canonicalized version as a fallback // this is required for friend declarations that occur before // the real declaration. there, the canonical cursor points to // the friend declaration which is not what we are looking for used = findDeclaration(referenced); } if (!used) { // as a last resort, try to resolve the forward declaration DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { auto *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: return visitor->buildCompoundStatement(cursor); case CXCursor_LambdaExpr: return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); // TODO: fix upstream and then just adapt this to UseCursorKind() case CXCursor_TypeAliasTemplateDecl: return visitor->dispatchTypeAliasTemplate(cursor, parent); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } } diff --git a/plugins/clang/duchain/types/classspecializationtype.h b/plugins/clang/duchain/types/classspecializationtype.h index eea89dc8ea..78775ed6a4 100644 --- a/plugins/clang/duchain/types/classspecializationtype.h +++ b/plugins/clang/duchain/types/classspecializationtype.h @@ -1,85 +1,85 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef CLASS_SPECIALIZATION_TYPE_H #define CLASS_SPECIALIZATION_TYPE_H #include #include using KDevelop::IndexedType; DECLARE_LIST_MEMBER_HASH(ClassSpecializationTypeData, parameters, IndexedType) struct ClassSpecializationTypeData : public KDevelop::StructureTypeData { ClassSpecializationTypeData(); ClassSpecializationTypeData(const ClassSpecializationTypeData& rhs); ~ClassSpecializationTypeData(); START_APPENDED_LISTS_BASE(ClassSpecializationTypeData, StructureTypeData); APPENDED_LIST_FIRST(ClassSpecializationTypeData, IndexedType, parameters); END_APPENDED_LISTS(ClassSpecializationTypeData, parameters); private: ClassSpecializationTypeData& operator=(const ClassSpecializationTypeData&); }; // This type represents a template class specialization. class ClassSpecializationType : public KDevelop::StructureType { public: ClassSpecializationType(const ClassSpecializationType& rhs); explicit ClassSpecializationType(ClassSpecializationTypeData& data); - typedef KDevelop::TypePtr Ptr; + using Ptr = KDevelop::TypePtr; ClassSpecializationType(); QString toString() const override; bool equals(const KDevelop::AbstractType * rhs) const override; QVector templateParameters() const; void addParameter(const KDevelop::IndexedType& param); void clearParameters(); KDevelop::AbstractType* clone() const override; uint hash() const override; enum { Identity = 18 }; - typedef ClassSpecializationTypeData Data; + using Data = ClassSpecializationTypeData; protected: TYPE_DECLARE_DATA(ClassSpecializationType); }; #endif // CLASS_SPECIALIZATION_TYPE_H diff --git a/plugins/clang/tests/test_problems.cpp b/plugins/clang/tests/test_problems.cpp index 90656c9481..f5ba24afac 100644 --- a/plugins/clang/tests/test_problems.cpp +++ b/plugins/clang/tests/test_problems.cpp @@ -1,506 +1,506 @@ /************************************************************************************* * Copyright (C) Kevin Funk * * * * 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 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 "test_problems.h" #include "../duchain/clangindex.h" #include "../duchain/clangproblem.h" #include "../duchain/parsesession.h" #include "../duchain/unknowndeclarationproblem.h" #include "../util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProblem::Severity) using namespace KDevelop; namespace { const QString FileName = #ifdef Q_OS_WIN QStringLiteral("C:/tmp/stdin.cpp"); #else QStringLiteral("/tmp/stdin.cpp"); #endif QList parse(const QByteArray& code) { ClangIndex index; ClangParsingEnvironment environment; environment.setTranslationUnitUrl(IndexedString(FileName)); ParseSession session(ParseSessionData::Ptr(new ParseSessionData({UnsavedFile(FileName, {code})}, &index, environment))); return session.problemsForFile(session.mainFile()); } void compareFixitWithoutDescription(const ClangFixit& a, const ClangFixit& b) { QCOMPARE(a.replacementText, b.replacementText); QCOMPARE(a.range, b.range); QCOMPARE(a.currentText, b.currentText); } void compareFixitsWithoutDescription(const ClangFixits& a, const ClangFixits& b) { if (a.size() != b.size()) { qDebug() << "a:" << a; qDebug() << "b:" << b; } QCOMPARE(a.size(), b.size()); const int size = a.size(); for (int i = 0; i < size; ++i) { compareFixitWithoutDescription(a.at(i), b.at(i)); } } } QTEST_GUILESS_MAIN(TestProblems) void TestProblems::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({"kdevclangsupport"}); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); CodeRepresentation::setDiskChangesForbidden(true); } void TestProblems::cleanupTestCase() { TestCore::shutdown(); } void TestProblems::testNoProblems() { const QByteArray code = "int main() {}"; auto problems = parse(code); QCOMPARE(problems.size(), 0); } void TestProblems::testBasicProblems() { // expected: // :1:13: error: expected ';' after class // class Foo {} // ^ // ; const QByteArray code = "class Foo {}"; auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems[0]->diagnostics().size(), 0); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(0, 12)); QCOMPARE(range.end(), KTextEditor::Cursor(0, 12)); } void TestProblems::testBasicRangeSupport() { // expected: // :1:17: warning: expression result unused [-Wunused-value] // int main() { (1 + 1); } // ~ ^ ~ const QByteArray code = "int main() { (1 + 1); }"; auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems[0]->diagnostics().size(), 0); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(0, 14)); QCOMPARE(range.end(), KTextEditor::Cursor(0, 19)); } void TestProblems::testChildDiagnostics() { // expected: // test.cpp:3:14: error: call to 'foo' is ambiguous // int main() { foo(0); } // ^~~ // test.cpp:1:6: note: candidate function // void foo(unsigned int); // ^ // test.cpp:2:6: note: candidate function // void foo(const char*); // ^ const QByteArray code = "void foo(unsigned int);\n" "void foo(const char*);\n" "int main() { foo(0); }"; auto problems = parse(code); QCOMPARE(problems.size(), 1); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(2, 13)); QCOMPARE(range.end(), KTextEditor::Cursor(2, 16)); QCOMPARE(problems[0]->diagnostics().size(), 2); IProblem::Ptr p1 = problems[0]->diagnostics()[0]; const ProblemPointer d1 = ProblemPointer(dynamic_cast(p1.data())); QCOMPARE(d1->url().str(), FileName); QCOMPARE(d1->rangeInCurrentRevision(), KTextEditor::Range(0, 5, 0, 8)); IProblem::Ptr p2 = problems[0]->diagnostics()[1]; const ProblemPointer d2 = ProblemPointer(dynamic_cast(p2.data())); QCOMPARE(d2->url().str(), FileName); QCOMPARE(d2->rangeInCurrentRevision(), KTextEditor::Range(1, 5, 1, 8)); } Q_DECLARE_METATYPE(QVector) /** * Provides a list of possible fixits: http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html */ void TestProblems::testFixits() { QFETCH(QString, code); QFETCH(int, problemsCount); QFETCH(QVector, fixits); auto problems = parse(code.toLatin1()); qDebug() << problems.last()->description(); QCOMPARE(problems.size(), problemsCount); const ClangProblem* p1 = dynamic_cast(problems[0].data()); QVERIFY(p1); auto* a1 = qobject_cast(p1->solutionAssistant().data()); QVERIFY(a1); QCOMPARE(p1->allFixits(), fixits); } void TestProblems::testFixits_data() { QTest::addColumn("code"); // input QTest::addColumn("problemsCount"); QTest::addColumn>("fixits"); // expected: // test -Wextra-tokens // /home/krf/test.cpp:2:8: warning: extra tokens at end of #endif directive [-Wextra-tokens] // #endif FOO // ^ // // QTest::newRow("extra-tokens test") << "#ifdef FOO\n#endif FOO\n" << 1 << QVector{ ClangFixit{"//", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 7, 1, 7)), QString()} }; // expected: // test.cpp:1:19: warning: empty parentheses interpreted as a function declaration [-Wvexing-parse] // int a(); // ^~ // test.cpp:1:19: note: replace parentheses with an initializer to declare a variable // int a(); // ^~ // = 0 QTest::newRow("vexing-parse test") << "int main() { int a(); }\n" << 1 << QVector{ ClangFixit{" = 0", DocumentRange(IndexedString(FileName), KTextEditor::Range(0, 18, 0, 20)), QString()} }; // expected: // test.cpp:2:21: error: no member named 'someVariablf' in 'C'; did you mean 'someVariable'? // int main() { C c; c.someVariablf = 1; } // ^~~~~~~~~~~~ // someVariable QTest::newRow("spell-check test") << "class C{ int someVariable; };\n" "int main() { C c; c.someVariablf = 1; }\n" << 1 << QVector{ ClangFixit{"someVariable", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 20, 1, 32)), QString()} }; } struct Replacement { QString string; QString replacement; }; using Replacements = QVector; ClangFixits resolveFilenames(const ClangFixits& fixits, const Replacements& replacements) { ClangFixits ret; for (const auto& fixit : fixits) { ClangFixit copy = fixit; for (const auto& replacement : replacements) { copy.replacementText.replace(replacement.string, replacement.replacement); copy.range.document = IndexedString(copy.range.document.str().replace(replacement.string, replacement.replacement)); } ret << copy; } return ret; } void TestProblems::testMissingInclude() { QFETCH(QString, includeFileContent); QFETCH(QString, workingFileContent); QFETCH(QString, dummyFileName); QFETCH(QVector, fixits); TestFile include(includeFileContent, QStringLiteral("h")); include.parse(TopDUContext::AllDeclarationsAndContexts); QScopedPointer dummyFile; if (!dummyFileName.isEmpty()) { dummyFile.reset(new QTemporaryFile(QDir::tempPath() + dummyFileName)); QVERIFY(dummyFile->open()); workingFileContent.replace(QLatin1String("dummyInclude"), dummyFile->fileName()); } TestFile workingFile(workingFileContent, QStringLiteral("cpp")); workingFile.parse(TopDUContext::AllDeclarationsAndContexts); QCOMPARE(include.url().toUrl().adjusted(QUrl::RemoveFilename), workingFile.url().toUrl().adjusted(QUrl::RemoveFilename)); QVERIFY(include.waitForParsed()); QVERIFY(workingFile.waitForParsed()); DUChainReadLocker lock; QVERIFY(include.topContext()); TopDUContext* includeTop = DUChainUtils::contentContextFromProxyContext(include.topContext().data()); QVERIFY(includeTop); QVERIFY(workingFile.topContext()); TopDUContext* top = DUChainUtils::contentContextFromProxyContext(workingFile.topContext()); QVERIFY(top); QCOMPARE(top->problems().size(), 1); auto problem = dynamic_cast(top->problems().first().data()); auto assistant = problem->solutionAssistant(); auto clangFixitAssistant = qobject_cast(assistant.data()); QVERIFY(clangFixitAssistant); auto resolvedFixits = resolveFilenames(fixits, { {"includeFile.h", include.url().toUrl().fileName()}, {"workingFile.h", workingFile.url().toUrl().fileName()} }); compareFixitsWithoutDescription(clangFixitAssistant->fixits(), resolvedFixits); } void TestProblems::testMissingInclude_data() { QTest::addColumn("includeFileContent"); QTest::addColumn("workingFileContent"); QTest::addColumn("dummyFileName"); QTest::addColumn>("fixits"); QTest::newRow("basic") << "class A {};\n" << "int main() { A a; }\n" << QString() << QVector{ ClangFixit{"class A;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()} }; // cf. bug 375274 QTest::newRow("ignore-moc-at-end") << "class Foo {};\n" << "#include \nint main() { Foo foo; }\n#include \"dummyInclude\"\n" << "/moc_fooXXXXXX.cpp" << QVector{ ClangFixit{"class Foo;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(1, 0, 1, 0)), QString()} }; QTest::newRow("ignore-moc-at-end2") << "class Foo {};\n" << "int main() { Foo foo; }\n#include \"dummyInclude\"\n" << "/fooXXXXXX.moc" << QVector{ ClangFixit{"class Foo;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()} }; } struct ExpectedTodo { QString description; KTextEditor::Cursor start; KTextEditor::Cursor end; }; -typedef QVector ExpectedTodos; +using ExpectedTodos = QVector; Q_DECLARE_METATYPE(ExpectedTodos) void TestProblems::testTodoProblems() { QFETCH(QString, code); QFETCH(ExpectedTodos, expectedTodos); TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto problems = top->problems(); QCOMPARE(problems.size(), expectedTodos.size()); for (int i = 0; i < problems.size(); ++i) { auto problem = problems[i]; auto expectedTodo = expectedTodos[i]; QCOMPARE(problem->description(), expectedTodo.description); QCOMPARE(problem->finalLocation().start(), expectedTodo.start); QCOMPARE(problem->finalLocation().end(), expectedTodo.end); } } void TestProblems::testTodoProblems_data() { QTest::addColumn("code"); QTest::addColumn("expectedTodos"); // we have two problems here: // - we cannot search for comments without declarations, // that means: we can only search inside doxygen-style comments // possible fix: -fparse-all-comments -- however: libclang API is lacking here again. // Can only search through comments attached to a valid entity in the AST // - we cannot detect the correct location of the comment yet // see more comments inside TodoExtractor QTest::newRow("simple1") << "/** TODO: Something */\n/** notodo */\n" << ExpectedTodos{{"TODO: Something", {0, 4}, {0, 19}}}; QTest::newRow("simple2") << "/// FIXME: Something\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("mixed-content") << "/// FIXME: Something\n///Uninteresting content\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("multi-line1") << "/**\n* foo\n*\n* FIXME: Something\n*/\n" << ExpectedTodos{{"FIXME: Something", {3, 2}, {3, 18}}}; QTest::newRow("multi-line2") << "/// FIXME: Something\n///Uninteresting content\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("multiple-todos-line2") << "/**\n* FIXME: one\n*foo bar\n* FIXME: two */\n" << ExpectedTodos{ {"FIXME: one", {1, 2}, {1, 12}}, {"FIXME: two", {3, 2}, {3, 12}} }; QTest::newRow("todo-later-in-the-document") << "///foo\n\n///FIXME: bar\n" << ExpectedTodos{{"FIXME: bar", {2, 3}, {2, 13}}}; QTest::newRow("non-ascii-todo") << "/* TODO: 例えば */" << ExpectedTodos{{"TODO: 例えば", {0, 3}, {0, 12}}}; } void TestProblems::testProblemsForIncludedFiles() { TestFile header(QStringLiteral("#pragma once\n//TODO: header\n"), QStringLiteral("h")); TestFile file("#include \"" + header.url().str() + "\"\n//TODO: source\n", QStringLiteral("cpp")); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST | TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto context = DUChain::self()->chainForDocument(file.url()); QVERIFY(context); QCOMPARE(context->problems().size(), 1); QCOMPARE(context->problems()[0]->description(), QStringLiteral("TODO: source")); QCOMPARE(context->problems()[0]->finalLocation().document, file.url()); context = DUChain::self()->chainForDocument(header.url()); QVERIFY(context); QCOMPARE(context->problems().size(), 1); QCOMPARE(context->problems()[0]->description(), QStringLiteral("TODO: header")); QCOMPARE(context->problems()[0]->finalLocation().document, header.url()); } } using RangeList = QVector; void TestProblems::testRanges_data() { QTest::addColumn("code"); QTest::addColumn("ranges"); { // expected: // test.cpp:4:1: error: C++ requires a type specifier for all declarations // operator[](int){return string;} // ^ // // test.cpp:4:24: error: 'string' does not refer to a value // operator[](int){return string;} // ^ const QByteArray code = "struct string{};\nclass Test{\npublic:\noperator[](int){return string;}\n};"; QTest::newRow("operator") << code << RangeList{{3, 0, 3, 8}, {3, 23, 3, 29}}; } { const QByteArray code = "#include \"/some/file/that/does/not/exist.h\"\nint main() { return 0; }"; QTest::newRow("badInclude") << code << RangeList{{0, 9, 0, 43}}; } { const QByteArray code = "int main() const\n{ return 0; }"; QTest::newRow("badConst") << code << RangeList{{0, 11, 0, 16}}; } } void TestProblems::testRanges() { QFETCH(QByteArray, code); QFETCH(RangeList, ranges); const auto problems = parse(code); RangeList actualRanges; for (auto problem : problems) { actualRanges << problem->rangeInCurrentRevision(); } qDebug() << actualRanges << ranges; QCOMPARE(actualRanges, ranges); } void TestProblems::testSeverity() { QFETCH(QByteArray, code); QFETCH(IProblem::Severity, severity); const auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems.at(0)->severity(), severity); } void TestProblems::testSeverity_data() { QTest::addColumn("code"); QTest::addColumn("severity"); QTest::newRow("error") << QByteArray("class foo {}") << IProblem::Error; QTest::newRow("warning") << QByteArray("int main() { int foo = 1 / 0; return foo; }") << IProblem::Warning; QTest::newRow("hint-unused-variable") << QByteArray("int main() { int foo = 0; return 0; }") << IProblem::Hint; QTest::newRow("hint-unused-parameter") << QByteArray("int main(int argc, char**) { return 0; }") << IProblem::Hint; } diff --git a/plugins/cmake/duchain/contextbuilder.h b/plugins/cmake/duchain/contextbuilder.h index ce4477f984..5b83ed1a72 100644 --- a/plugins/cmake/duchain/contextbuilder.h +++ b/plugins/cmake/duchain/contextbuilder.h @@ -1,39 +1,39 @@ /* KDevelop CMake Support * * Copyright 2014 Aleix Pol * * 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 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 CONTEXTBUILDER_H #define CONTEXTBUILDER_H #include #include -typedef QVectorIterator CMakeContentIterator; +using CMakeContentIterator = QVectorIterator; class ContextBuilder : public KDevelop::AbstractContextBuilder { public: KDevelop::DUContext* contextFromNode(CMakeContentIterator* node) override; KDevelop::RangeInRevision editorFindRange(CMakeContentIterator* fromNode, CMakeContentIterator* toNode) override; KDevelop::QualifiedIdentifier identifierForNode(CMakeFunctionDesc* node) override; void setContextOnNode(CMakeContentIterator* node, KDevelop::DUContext* context) override; KDevelop::TopDUContext* newTopContext(const KDevelop::RangeInRevision& range, KDevelop::ParsingEnvironmentFile* file = nullptr) override; }; #endif // CONTEXTBUILDER_H diff --git a/plugins/cmake/duchain/declarationbuilder.h b/plugins/cmake/duchain/declarationbuilder.h index a0dc794e18..6c317b87fb 100644 --- a/plugins/cmake/duchain/declarationbuilder.h +++ b/plugins/cmake/duchain/declarationbuilder.h @@ -1,38 +1,38 @@ /* KDevelop CMake Support * * Copyright 2014 Aleix Pol * * 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 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 DECLARATIONBUILDER_H #define DECLARATIONBUILDER_H #include #include "contextbuilder.h" #include #include -typedef KDevelop::AbstractDeclarationBuilder DeclarationBuilderBase; +using DeclarationBuilderBase = KDevelop::AbstractDeclarationBuilder; class DeclarationBuilder : public DeclarationBuilderBase { public: // virtual KDevelop::ReferencedTopDUContext build(const KDevelop::IndexedString& url, CMakeFunctionDesc* node, KDevelop::ReferencedTopDUContext updateContext); void startVisiting(CMakeContentIterator* node) override; }; #endif // DECLARATIONBUILDER_H diff --git a/plugins/cmake/parser/cmakelistsparser.h b/plugins/cmake/parser/cmakelistsparser.h index 17a53b5740..e6c8f00778 100644 --- a/plugins/cmake/parser/cmakelistsparser.h +++ b/plugins/cmake/parser/cmakelistsparser.h @@ -1,126 +1,126 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007 Aleix Pol Gonzalez * * 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 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 CMAKELISTSPARSER_H #define CMAKELISTSPARSER_H #include #include #include #include "cmListFileLexer.h" #include #include class QStringList; struct CMakeFunctionArgument { CMakeFunctionArgument() {} explicit CMakeFunctionArgument(const QString& v); CMakeFunctionArgument(const QString& v, bool q, quint32 l = 0, quint32 c=0); inline bool operator == (const CMakeFunctionArgument& r) const { return (this->value == r.value) && (this->quoted == r.quoted); } inline bool operator != (const CMakeFunctionArgument& r) const { return (this->value != r.value) || (this->quoted != r.quoted); } bool operator==( const QString& rhs ) { return ( this->value == rhs ); } bool isCorrect() const { return column>0; } static QString unescapeValue(const QString& value); KDevelop::RangeInRevision range() const { return KDevelop::RangeInRevision(line-1, column-1, line-1, column+value.length()-1); } bool isValid() const { return quoted || !value.isEmpty(); } QString value; bool quoted = false; quint32 line = 0; quint32 column = 0; static const QMap scapings; }; Q_DECLARE_METATYPE( CMakeFunctionArgument ) class KDEVCMAKECOMMON_EXPORT CMakeFunctionDesc { public: CMakeFunctionDesc(); ///useful when writing unit tests mostly CMakeFunctionDesc(const QString& name, const QStringList& args); QString name; QVector arguments; QString filePath; quint32 line = 0; quint32 column = 0; quint32 endLine = 0; quint32 endColumn = 0; KDevelop::RangeInRevision nameRange() const { return KDevelop::RangeInRevision(line-1, column-1, line-1, column-1+name.length()); } KDevelop::RangeInRevision range() const { return KDevelop::RangeInRevision(line-1, column-1, endLine-1, endColumn); } KDevelop::RangeInRevision argRange() const { if( !arguments.isEmpty() ) { return KDevelop::RangeInRevision(arguments.first().range().start, arguments.last().range().end); } else { return KDevelop::RangeInRevision( line-1, column-1, endLine-1, endColumn); } } bool operator==(const CMakeFunctionDesc &other) const; void addArguments( const QStringList&, bool addEvenIfEmpty=true ); QString writeBack() const; }; Q_DECLARE_METATYPE( CMakeFunctionDesc ) /** * CMake files function descriptor collector * @author Matt Rogers * @author Aleix Pol */ -typedef QVector CMakeFileContent; +using CMakeFileContent = QVector; namespace CMakeListsParser { KDEVCMAKECOMMON_EXPORT CMakeFileContent readCMakeFile(const QString& fileName); } #endif diff --git a/plugins/cmake/parser/cmaketypes.h b/plugins/cmake/parser/cmaketypes.h index b4d11a9910..6cb5610bce 100644 --- a/plugins/cmake/parser/cmaketypes.h +++ b/plugins/cmake/parser/cmaketypes.h @@ -1,88 +1,88 @@ /* Copyright 2009 Aleix Pol Gonzalez 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 CMAKETYPES_H #define CMAKETYPES_H #include "cmakelistsparser.h" #include #include #include struct Macro { QString name; QStringList knownArgs; CMakeFileContent code; bool isFunction; }; struct CacheEntry { explicit CacheEntry(const QString& value=QString(), const QString &doc=QString()) : value(value), doc(doc) {} QString value; QString doc; }; struct Target { - typedef QMap Properties; + using Properties = QMap; enum Type { Library, Executable, Custom }; KDevelop::IndexedDeclaration declaration; QStringList files; Type type; CMakeFunctionDesc desc; QString name; }; struct Subdirectory { QString name; CMakeFunctionDesc desc; QString build_dir; }; struct Test { Test() {} QString name; KDevelop::Path executable; QStringList arguments; QHash properties; }; Q_DECLARE_TYPEINFO(Test, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(Subdirectory, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(Target, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(CacheEntry, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(Macro, Q_MOVABLE_TYPE); enum PropertyType { GlobalProperty, DirectoryProperty, TargetProperty, SourceProperty, TestProperty, CacheProperty, VariableProperty }; -typedef QHash > CategoryType; -typedef QMap CMakeProperties; +using CategoryType = QHash>; +using CMakeProperties = QMap; -typedef QHash MacroMap; -typedef QHash CMakeDefinitions; -typedef QHash CacheValues; +using MacroMap = QHash; +using CMakeDefinitions = QHash; +using CacheValues = QHash; Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(PropertyType) #endif diff --git a/plugins/custom-definesandincludes/compilerprovider/icompiler.h b/plugins/custom-definesandincludes/compilerprovider/icompiler.h index d01f2f0337..57e491e574 100644 --- a/plugins/custom-definesandincludes/compilerprovider/icompiler.h +++ b/plugins/custom-definesandincludes/compilerprovider/icompiler.h @@ -1,102 +1,102 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef ICOMPILER_H #define ICOMPILER_H #include #include #include "../idefinesandincludesmanager.h" namespace Utils { enum LanguageType { C, Cpp, OpenCl, Cuda, ObjC, ObjCpp, Other }; } /// An interface that represents a compiler. Compiler provides standard include directories and standard defined macros. class ICompiler { public: /** * @param name The user visible name * @param path path to the compiler * @param factoryName name of the factory that created this compiler * @param editable whether user can change the name and the path to the compiler (should be set to false for automatically detected compilers) **/ ICompiler( const QString& name, const QString& path, const QString& factoryName, bool editable ); /** * @param type Language type, must not be @ref Utils::Other. * @param arguments compiler command-line arguments * @return list of defined macros for the compiler */ virtual KDevelop::Defines defines(Utils::LanguageType type, const QString& arguments) const = 0; /** * @param type Language type, must not be @ref Utils::Other. * @param arguments compiler command-line arguments * @return list of include directories for the compiler */ virtual KDevelop::Path::List includes(Utils::LanguageType type, const QString& arguments) const = 0; void setPath( const QString &path ); /// @return path to the compiler QString path() const; void setName( const QString &name ); /// @return user visible name QString name() const; /// Indicates if the compiler name/path can be set manually bool editable() const; /// @return name of the factory that created this compiler QString factoryName() const; virtual ~ICompiler() = default; private: bool m_editable; QString m_name; QString m_path; QString m_factoryName; }; -typedef QSharedPointer CompilerPointer; +using CompilerPointer = QSharedPointer; Q_DECLARE_METATYPE(CompilerPointer) #endif // ICOMPILER_H diff --git a/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h b/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h index c0871b70e9..05155334d8 100644 --- a/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h +++ b/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h @@ -1,52 +1,52 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef ICOMPILERFACTORY_H #define ICOMPILERFACTORY_H #include "icompiler.h" class CompilerProvider; /// Interface that represents a factory for creating compilers class ICompilerFactory { public: virtual QString name() const = 0; ///@return new compiler ///@see ICompiler virtual CompilerPointer createCompiler( const QString& name, const QString& path, bool editable = true ) const = 0; /** * registers default compilers for the @p provider * E.g. for gcc default compilers could be "gcc c99" and "gcc c++11" */ virtual void registerDefaultCompilers(CompilerProvider* provider) const = 0; virtual ~ICompilerFactory() = default; }; -typedef QSharedPointer CompilerFactoryPointer; +using CompilerFactoryPointer = QSharedPointer; #endif // ICOMPILERFACTORY_H diff --git a/plugins/custommake/makefileresolver/makefileresolver.cpp b/plugins/custommake/makefileresolver/makefileresolver.cpp index 26da0df832..9039d35ad7 100644 --- a/plugins/custommake/makefileresolver/makefileresolver.cpp +++ b/plugins/custommake/makefileresolver/makefileresolver.cpp @@ -1,639 +1,639 @@ /* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * Copyright 2014 Kevin Funk * * 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 "makefileresolver.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include // #define VERBOSE #if defined(VERBOSE) #define ifTest(x) x #else #define ifTest(x) #endif const int maximumInternalResolutionDepth = 3; using namespace std; using namespace KDevelop; namespace { ///After how many seconds should we retry? static const int CACHE_FAIL_FOR_SECONDS = 200; static const int processTimeoutSeconds = 30; struct CacheEntry { CacheEntry() { } ModificationRevisionSet modificationTime; Path::List paths; Path::List frameworkDirectories; QHash defines; QString errorMessage, longErrorMessage; bool failed = false; QMap failedFiles; QDateTime failTime; }; - typedef QMap Cache; + using Cache = QMap; static Cache s_cache; static QMutex s_cacheMutex; } /** * Compatibility: * make/automake: Should work perfectly * cmake: Thanks to the path-recursion, this works with cmake(tested with version "2.4-patch 6" * with kdelibs out-of-source and with kdevelop4 in-source) **/ class SourcePathInformation { public: explicit SourcePathInformation(const QString& path) : m_path(path) { } QString createCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const { QString relativeFile = Path(workingDirectory).relativePath(Path(absoluteFile)); #ifndef Q_OS_FREEBSD // GNU make implicitly enables "-w" for sub-makes, we don't want that QLatin1String noPrintDirFlag = QLatin1String(" --no-print-directory"); #else QLatin1String noPrintDirFlag; #endif return QLatin1String("make -k") + noPrintDirFlag + QLatin1String(" -W \'") + absoluteFile + QLatin1String("\' -W \'") + relativeFile + QLatin1String("\' -n ") + makeParameters; } bool hasMakefile() const { QFileInfo makeFile(m_path, QStringLiteral("Makefile")); return makeFile.exists(); } QStringList possibleTargets(const QString& targetBaseName) const { const QStringList ret{ ///@todo open the make-file, and read the target-names from there. //It would be nice if all targets could be processed in one call, the problem is the exit-status of make, so for now make has to be called multiple times. targetBaseName + QLatin1String(".o"), targetBaseName + QLatin1String(".lo"), //ret << targetBaseName + ".lo " + targetBaseName + ".o"; targetBaseName + QLatin1String(".ko"), }; return ret; } private: QString m_path; }; static void mergePaths(KDevelop::Path::List& destList, const KDevelop::Path::List& srcList) { for (const Path& path : srcList) { if (!destList.contains(path)) destList.append(path); } } void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) { mergePaths(paths, rhs.paths); mergePaths(frameworkDirectories, rhs.frameworkDirectories); includePathDependency += rhs.includePathDependency; defines.unite(rhs.defines); } PathResolutionResult::PathResolutionResult(bool success, const QString& errorMessage, const QString& longErrorMessage) : success(success) , errorMessage(errorMessage) , longErrorMessage(longErrorMessage) {} PathResolutionResult::operator bool() const { return success; } ModificationRevisionSet MakeFileResolver::findIncludePathDependency(const QString& file) { QString oldSourceDir = m_source; QString oldBuildDir = m_build; Path currentWd(mapToBuild(file)); ModificationRevisionSet rev; while (currentWd.hasParent()) { currentWd = currentWd.parent(); QString path = currentWd.toLocalFile(); QFileInfo makeFile(QDir(path), QStringLiteral("Makefile")); if (makeFile.exists()) { IndexedString makeFileStr(makeFile.filePath()); rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); break; } } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); return rev; } bool MakeFileResolver::executeCommand(const QString& command, const QString& workingDirectory, QString& result) const { ifTest(cout << "executing " << command.toUtf8().constData() << endl); ifTest(cout << "in " << workingDirectory.toUtf8().constData() << endl); KProcess proc; proc.setWorkingDirectory(workingDirectory); proc.setOutputChannelMode(KProcess::MergedChannels); QStringList args(command.split(QLatin1Char(' '))); QString prog = args.takeFirst(); proc.setProgram(prog, args); int status = proc.execute(processTimeoutSeconds * 1000); result = QString::fromUtf8(proc.readAll()); return status == 0; } MakeFileResolver::MakeFileResolver() { } ///More efficient solution: Only do exactly one call for each directory. During that call, mark all source-files as changed, and make all targets for those files. PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file) { if (file.isEmpty()) { // for unit tests with temporary files return PathResolutionResult(); } QFileInfo fi(file); return resolveIncludePath(fi.fileName(), fi.absolutePath()); } QString MakeFileResolver::mapToBuild(const QString &path) const { QString wd = QDir::cleanPath(path); if (m_outOfSource) { if (wd.startsWith(m_source) && !wd.startsWith(m_build)) { //Move the current working-directory out of source, into the build-system wd = QDir::cleanPath(m_build + QLatin1Char('/') + wd.midRef(m_source.length())); } } return wd; } void MakeFileResolver::clearCache() { QMutexLocker l(&s_cacheMutex); s_cache.clear(); } PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file, const QString& _workingDirectory, int maxStepsUp) { //Prefer this result when returning a "fail". The include-paths of this result will always be added. PathResolutionResult resultOnFail; if (m_isResolving) return PathResolutionResult(false, i18n("Tried include path resolution while another resolution process was still running")); //Make the working-directory absolute QString workingDirectory = _workingDirectory; if (QFileInfo(workingDirectory).isRelative()) { QUrl u = QUrl::fromLocalFile(QDir::currentPath()); if (workingDirectory == QLatin1String(".")) workingDirectory = QString(); else if (workingDirectory.startsWith(QLatin1String("./"))) workingDirectory.remove(0, 2); if (!workingDirectory.isEmpty()) { u = u.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path() + QLatin1Char('/') + workingDirectory); } workingDirectory = u.toLocalFile(); } else workingDirectory = _workingDirectory; ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) QDir sourceDir(workingDirectory); QDir dir = QDir(mapToBuild(sourceDir.absolutePath())); QFileInfo makeFile(dir, QStringLiteral("Makefile")); if (!makeFile.exists()) { if (maxStepsUp > 0) { //If there is no makefile in this directory, go one up and re-try from there QFileInfo fileName(file); QString localName = sourceDir.dirName(); if (sourceDir.cdUp() && !fileName.isAbsolute()) { const QString checkFor = localName + QLatin1Char('/') + file; PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); if (oneUp.success) { oneUp.mergeWith(resultOnFail); return oneUp; } } } if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Makefile is missing in folder \"%1\"", dir.absolutePath()), i18n("Problem while trying to resolve include paths for %1", file)); } PushValue e(m_isResolving, true); Path::List cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version Path::List cachedFWDirs; QHash cachedDefines; ModificationRevisionSet dependency; dependency.addModificationRevision(IndexedString(makeFile.filePath()), ModificationRevision::revisionForFile(IndexedString(makeFile.filePath()))); dependency += resultOnFail.includePathDependency; Cache::iterator it; { QMutexLocker l(&s_cacheMutex); it = s_cache.find(dir.path()); if (it != s_cache.end()) { cachedPaths = it->paths; cachedFWDirs = it->frameworkDirectories; cachedDefines = it->defines; if (dependency == it->modificationTime) { if (!it->failed) { //We have a valid cached result PathResolutionResult ret(true); ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //We have a cached failed result. We should use that for some time but then try again. Return the failed result if: (there were too many tries within this folder OR this file was already tried) AND The last tries have not expired yet if (/*(it->failedFiles.size() > 3 || it->failedFiles.find(file) != it->failedFiles.end()) &&*/ it->failTime.secsTo(QDateTime::currentDateTime()) < CACHE_FAIL_FOR_SECONDS) { PathResolutionResult ret(false); //Fake that the result is ok ret.errorMessage = i18n("Cached: %1", it->errorMessage); ret.longErrorMessage = it->longErrorMessage; ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //Try getting a correct result again } } } } } ///STEP 1: Prepare paths QString targetName; QFileInfo fi(file); QString absoluteFile = file; if (fi.isRelative()) absoluteFile = workingDirectory + QLatin1Char('/') + file; absoluteFile = QDir::cleanPath(absoluteFile); int dot; if ((dot = file.lastIndexOf(QLatin1Char('.'))) == -1) { if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); } targetName = file.left(dot); QString wd = dir.path(); if (QFileInfo(wd).isRelative()) { wd = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + wd); } wd = mapToBuild(wd); SourcePathInformation source(wd); QStringList possibleTargets = source.possibleTargets(targetName); ///STEP 3: Try resolving the paths, by using once the absolute and once the relative file-path. Which kind is required differs from setup to setup. ///STEP 3.1: Try resolution using the absolute path PathResolutionResult res; //Try for each possible target res = resolveIncludePathInternal(absoluteFile, wd, possibleTargets.join(QLatin1Char(' ')), source, maximumInternalResolutionDepth); if (!res) { ifTest(cout << "Try for absolute file " << absoluteFile.toLocal8Bit().data() << " and targets " << possibleTargets.join(", ").toLocal8Bit().data() << " failed: " << res.longErrorMessage.toLocal8Bit().data() << endl;) } res.includePathDependency = dependency; if (res.paths.isEmpty()) { res.paths = cachedPaths; //We failed, maybe there is an old cached result, use that. res.defines = cachedDefines; } // a build command could contain only one or more -iframework or -F specifications. if (res.frameworkDirectories.isEmpty()) { res.frameworkDirectories = cachedFWDirs; } { QMutexLocker l(&s_cacheMutex); if (it == s_cache.end()) it = s_cache.insert(dir.path(), CacheEntry()); CacheEntry& ce(*it); ce.paths = res.paths; ce.frameworkDirectories = res.frameworkDirectories; ce.modificationTime = dependency; if (!res) { ce.failed = true; ce.errorMessage = res.errorMessage; ce.longErrorMessage = res.longErrorMessage; ce.failTime = QDateTime::currentDateTime(); ce.failedFiles[file] = true; } else { ce.failed = false; ce.failedFiles.clear(); } } if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty())) return resultOnFail; return res; } static QRegularExpression includeRegularExpression() { static const QRegularExpression expression(QStringLiteral( "\\s(--include-dir=|-I\\s*|-isystem\\s+|-iframework\\s+|-F\\s*)(" "\\'.*\\'|\\\".*\\\"" //Matches "hello", 'hello', 'hello"hallo"', etc. "|" "((?:\\\\.)?([\\S^\\\\]?))+" //Matches /usr/I\ am\ a\ strange\ path/include ")(?=\\s)" )); Q_ASSERT(expression.isValid()); return expression; } PathResolutionResult MakeFileResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth) { --maxDepth; if (maxDepth < 0) return PathResolutionResult(false); QString fullOutput; executeCommand(source.createCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); { QRegExp newLineRx(QStringLiteral("\\\\\\n")); fullOutput.remove(newLineRx); } ///@todo collect multiple outputs at the same time for performance-reasons QString firstLine = fullOutput; int lineEnd; if ((lineEnd = fullOutput.indexOf(QLatin1Char('\n'))) != -1) firstLine.truncate(lineEnd); //Only look at the first line of output /** * There's two possible cases this can currently handle. * 1.: gcc is called, with the parameters we are searching for (so we parse the parameters) * 2.: A recursive make is called, within another directory(so we follow the recursion and try again) "cd /foo/bar && make -f pi/pa/build.make pi/pa/po.o * */ ///STEP 1: Test if it is a recursive make-call // Do not search for recursive make-calls if we already have include-paths available. Happens in kernel modules. if (!includeRegularExpression().match(fullOutput).hasMatch()) { QRegExp makeRx(QStringLiteral("\\bmake\\s")); int offset = 0; while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { QString prefix = firstLine.left(offset).trimmed(); if (prefix.endsWith(QLatin1String("&&")) || prefix.endsWith(QLatin1Char(';')) || prefix.isEmpty()) { QString newWorkingDirectory = workingDirectory; ///Extract the new working-directory if (!prefix.isEmpty()) { if (prefix.endsWith(QLatin1String("&&"))) prefix.truncate(prefix.length() - 2); else if (prefix.endsWith(QLatin1Char(';'))) prefix.truncate(prefix.length() - 1); ///Now test if what we have as prefix is a simple "cd /foo/bar" call. //In cases like "cd /media/data/kdedev/4.0/build/kdevelop && cd /media/data/kdedev/4.0/build/kdevelop" //We use the second directory. For t hat reason we search for the last index of "cd " int cdIndex = prefix.lastIndexOf(QLatin1String("cd ")); if (cdIndex != -1) { newWorkingDirectory = prefix.mid(cdIndex + 3).trimmed(); if (QFileInfo(newWorkingDirectory).isRelative()) newWorkingDirectory = workingDirectory + QLatin1Char('/') + newWorkingDirectory; newWorkingDirectory = QDir::cleanPath(newWorkingDirectory); } } if (newWorkingDirectory == workingDirectory) { return PathResolutionResult(false, i18n("Failed to extract new working directory"), i18n("Output was: %1", fullOutput)); } QFileInfo d(newWorkingDirectory); if (d.exists()) { ///The recursive working-directory exists. QString makeParams = firstLine.mid(offset+5); if (!makeParams.contains(QLatin1Char(';')) && !makeParams.contains(QLatin1String("&&"))) { ///Looks like valid parameters ///Make the file-name absolute, so it can be referenced from any directory QString absoluteFile = file; if (QFileInfo(absoluteFile).isRelative()) absoluteFile = workingDirectory + QLatin1Char('/') + file; Path absolutePath(absoluteFile); ///Try once with absolute, and if that fails with relative path of the file SourcePathInformation newSource(newWorkingDirectory); PathResolutionResult res = resolveIncludePathInternal(absolutePath.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); if (res) return res; return resolveIncludePathInternal(Path(newWorkingDirectory).relativePath(absolutePath), newWorkingDirectory, makeParams , newSource, maxDepth); }else{ return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The parameter string \"%1\" does not seem to be valid. Output was: %2.", makeParams, fullOutput)); } } else { return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The directory \"%1\" does not exist. Output was: %2.", newWorkingDirectory, fullOutput)); } } else { return PathResolutionResult(false, i18n("Malformed recursive make call"), i18n("Output was: %1", fullOutput)); } ++offset; if (offset >= firstLine.length()) break; } } ///STEP 2: Search the output for include-paths PathResolutionResult ret = processOutput(fullOutput, workingDirectory); if (ret.paths.isEmpty() && ret.frameworkDirectories.isEmpty()) return PathResolutionResult(false, i18n("Could not extract include paths from make output"), i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, source.createCommand(file, workingDirectory, makeParameters), fullOutput)); return ret; } QRegularExpression MakeFileResolver::defineRegularExpression() { static const QRegularExpression pattern( QStringLiteral("-D([^\\s=]+)(?:=(?:\"(.*?)(? 2)) { //probable a quoted path if (path.endsWith(path.leftRef(1))) { //Quotation is ok, remove it path = path.mid(1, path.length() - 2); } } if (QDir::isRelativePath(path)) path = workingDirectory + QLatin1Char('/') + path; const auto& internedPath = internPath(path); const auto& type = match.captured(1); const auto isFramework = type.startsWith(QLatin1String("-iframework")) || type.startsWith(QLatin1String("-F")); if (isFramework) { ret.frameworkDirectories << internedPath; } else { ret.paths << internedPath; } } } { const auto& defineRx = defineRegularExpression(); auto it = defineRx.globalMatch(fullOutput); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret.defines[internString(match.captured(1))] = internString(value); } } return ret; } void MakeFileResolver::resetOutOfSourceBuild() { m_outOfSource = false; } void MakeFileResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) { if (source == build) { resetOutOfSourceBuild(); return; } m_outOfSource = true; m_source = QDir::cleanPath(source); m_build = QDir::cleanPath(m_build); } Path MakeFileResolver::internPath(const QString& path) const { Path& ret = m_pathCache[path]; if (ret.isEmpty() != path.isEmpty()) { ret = Path(path); } return ret; } QString MakeFileResolver::internString(const QString& path) const { auto it = m_stringCache.constFind(path); if (it != m_stringCache.constEnd()) { return *it; } m_stringCache.insert(path); return path; } diff --git a/plugins/debuggercommon/mi/micommand.h b/plugins/debuggercommon/mi/micommand.h index 49a34b919c..9ab5d734a5 100644 --- a/plugins/debuggercommon/mi/micommand.h +++ b/plugins/debuggercommon/mi/micommand.h @@ -1,379 +1,379 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org copyright : (C) 2016 by Aetf email : aetf@unlimitedcodeworks.xyz ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef _MICOMMAND_H_ #define _MICOMMAND_H_ #include "mi/mi.h" #include #include #include #include namespace KDevMI { class MIDebugSession; namespace MI { class VarItem; class ValueCallback; enum CommandFlag { /// The command handler also wishes to receive an error responses, overriding the default error handler CmdHandlesError = 1 << 0, /// The command is expected to cause the inferior to run. Controllers that display the /// program's state should refrain from sending commands while a command with this flag /// is currently pending; however, note that a command with this flag needn't be guaranteed /// to lead to a running state. CmdMaybeStartsRunning = 1 << 1, /// The command is a temporary-run type command, meaning that it typically causes the program /// to run, but only for a short time before it stops again (e.g. Step and StepInto-type /// commands). When the program is running due to this type of command, a CmdImmediately /// command will wait before forcing an interrupt of the debugger, and the program is _not_ /// automatically restarted if an interrupt was forced. /// /// TODO: this special handling has not actually been implemented yet CmdTemporaryRun = 1 << 2, /// This command should be executed immediately, even if the program is currently running /// (e.g. breakpoint setting and modification); however, if the program is interrupted, /// it should be resumed after this command has run. CmdImmediately = 1 << 3, /// This is a command that should interrupt a running program, without resuming. CmdInterrupt = 1 << 4, }; Q_DECLARE_FLAGS(CommandFlags, CommandFlag) //base class for handlers class MICommandHandler { public: virtual ~MICommandHandler() {} virtual void handle(const ResultRecord&) = 0; virtual bool handlesError() { return false; } /** * If the handler object should be deleted after the handle() call. */ virtual bool autoDelete() { return true; } }; class FunctionCommandHandler : public MICommandHandler { public: - typedef std::function Function; + using Function = std::function; explicit FunctionCommandHandler(const Function& callback, CommandFlags flags = {}); void handle(const ResultRecord&) override; bool handlesError() override; private: CommandFlags _flags; Function _callback; }; /** * @author John Birch */ class MICommand { protected: explicit MICommand(CommandType type, const QString& arguments = QString(), CommandFlags flags = {}); friend class KDevMI::MIDebugSession; public: virtual ~MICommand(); CommandType type() const; virtual QString miCommand() const; CommandFlags flags() const {return flags_;} /** * Returns the MI token with which the command is sent, allowing the parser to match up * the result message with the command. */ uint32_t token() const {return token_;} /** * Set the MI token. This is done by \ref MICommandQueue. */ void setToken(uint32_t token) {token_ = token;} /** * Returns the thread that needs to be currently selected when this command is executed, * or -1 if there is no requirement. */ int thread() const; /** * Set the thread required to be currently selected when the command is executed. */ void setThread(int thread); /** * Returns the frame that needs to be currently selected when this command is executed, * or -1 if there is no requirement. */ int frame() const; /** * Set the frame required to be currently selected when the command is executed. */ void setFrame(int frame); /** * Sets the handler for results. * * The command object assumes ownership of @p handler. */ void setHandler(MICommandHandler* handler); void setHandler(const FunctionCommandHandler::Function &callback); template void setHandler(Handler* handler_this, void (Handler::* handler_method)(const ResultRecord&)); /* The command that should be sent to debugger. This method is virtual so the command can compute this dynamically, possibly using results of the previous commands. If the empty string is returned, nothing is sent. */ virtual QString cmdToSend(); /* Returns the initial string that was specified in ctor invocation. The actual command will be determined by cmdToSend above and the return value of this method is only used in various diagnostic messages emitted before actually sending the command. */ QString initialString() const; /* Returns true if this is command entered by the user and so should be always shown in the gdb output window. */ virtual bool isUserCommand() const; // If there's a handler for this command, invokes it and returns true. // Otherwise, returns false. bool invokeHandler(const ResultRecord& r); // Returns 'true' if 'invokeHandler' should be invoked even // on MI errors. bool handlesError() const; // Called by debuggercontroller for each new output string // debugger emits for this command. In MI mode, this includes // all "stream" messages, but does not include MI responses. void newOutput(const QString&); const QStringList& allStreamOutput() const; QString command() const; void setStateReloading(bool f); bool stateReloading() const; /// Called when the command has been enqueued in the debug session /// and the command is wait for being submitted to GDB. void markAsEnqueued(); /// Called when the command has been submitted to GDB and the command /// waits for completion by GDB. void markAsSubmitted(); /// Called when the command has been completed and the response has arrived. void markAsCompleted(); /// returns the amount of time (in ms) passed between submission and completion. qint64 gdbProcessingTime() const; /// returns the amount of time (in ms) passed between enqueuing and submission. qint64 queueTime() const; /// returns the amount of time (in ms) passed between enqueuing and completion. qint64 totalProcessingTime() const; protected: CommandType type_; CommandFlags flags_; uint32_t token_ = 0; QString command_; MICommandHandler *commandHandler_; QStringList lines; bool stateReloading_; int m_thread; int m_frame; // remember the timestamps (in ms since start of the epoch) when this command // - was added to the command queue (enqueued) // - was submitted to GDB // - was completed; response from GDB arrived qint64 m_enqueueTimestamp; qint64 m_submitTimestamp; qint64 m_completeTimestamp; }; class UserCommand : public MICommand { public: UserCommand(CommandType type, const QString& s); bool isUserCommand() const override; }; /** This is a class for raw CLI commands. Instead of invoking user provided hook with MI response, it invokes the a hook with lists of strings. */ class CliCommand : public MICommand { public: template CliCommand(CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags = {}); }; /** Command that does nothing and can be just used to invoke a user provided handler when all preceding commands are executed. */ class SentinelCommand : public MICommand { public: - typedef std::function Function; + using Function = std::function; template SentinelCommand(Handler* handler_this, void (Handler::* handler_method)(), CommandFlags flags = {}) : MICommand(NonMI, QString(), flags) { QPointer guarded_this(handler_this); handler = [guarded_this, handler_method]() { if (guarded_this) { (guarded_this.data()->*handler_method)(); } }; } explicit SentinelCommand(const Function& handler, CommandFlags flags = {}) : MICommand(NonMI, QString(), flags) , handler(handler) { } using MICommand::invokeHandler; void invokeHandler() { handler(); } QString cmdToSend() override { return QString(); } private: Function handler; }; class ExpressionValueCommand : public QObject, public MICommand { Q_OBJECT public: - typedef void (QObject::*handler_method_t)(const QString&); + using handler_method_t = void (QObject::*)(const QString&); template ExpressionValueCommand( const QString& expression, Handler* handler_this, void (Handler::* handler_method)(const QString&)) : MICommand(DataEvaluateExpression, expression), handler_this(handler_this), handler_method(static_cast(handler_method)) { setHandler(this, &ExpressionValueCommand::handleResponse); } void handleResponse(const ResultRecord& r) { (handler_this.data()->*handler_method)(r[QStringLiteral("value")].literal()); } private: QPointer handler_this; handler_method_t handler_method; }; template FunctionCommandHandler::Function guarded_callback(Handler *handler_this, void (Handler::* handler_method)(const ResultRecord&)) { QPointer guarded_this(handler_this); return [guarded_this, handler_method](const ResultRecord& r) { if (guarded_this) { (guarded_this.data()->*handler_method)(r); } }; } template void MICommand::setHandler(Handler* handler_this, void (Handler::* handler_method)(const ResultRecord&)) { QPointer guarded_this(handler_this); setHandler(new FunctionCommandHandler([guarded_this, handler_method](const ResultRecord& r) { if (guarded_this) { (guarded_this.data()->*handler_method)(r); } }, flags())); } template CliCommand::CliCommand( CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags) : MICommand(type, command) { QPointer guarded_this(handler_this); setHandler(new FunctionCommandHandler([this, guarded_this, handler_method](const ResultRecord&) { if (guarded_this) { (guarded_this.data()->*handler_method)(this->allStreamOutput()); } }, flags)); } } // end of namespace MI } // end of namespace KDevMI Q_DECLARE_OPERATORS_FOR_FLAGS(KDevMI::MI::CommandFlags) #endif diff --git a/plugins/debuggercommon/mi/milexer.h b/plugins/debuggercommon/mi/milexer.h index f871fac46f..aeba4d0a98 100644 --- a/plugins/debuggercommon/mi/milexer.h +++ b/plugins/debuggercommon/mi/milexer.h @@ -1,150 +1,150 @@ /*************************************************************************** * Copyright (C) 2004 by Roberto Raggi * * roberto@kdevelop.org * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MILEXER_H #define MILEXER_H #include namespace KDevMI { namespace MI { class MILexer; struct TokenStream; -typedef void (MILexer::*scan_fun_ptr)(int *kind); +using scan_fun_ptr = void (MILexer::*)(int*); struct Token { int kind; int position; int length; }; struct FileSymbol { QByteArray contents; TokenStream *tokenStream = nullptr; inline FileSymbol() {} inline ~FileSymbol(); }; struct TokenStream { inline int lookAhead(int n = 0) const { return (m_currentToken + n)->kind; } inline int currentToken() const { return m_currentToken->kind; } inline QByteArray currentTokenText() const { return tokenText(-1); } QByteArray tokenText(int index = 0) const; inline int lineOffset(int line) const { return m_lines.at(line); } void positionAt(int position, int *line, int *column) const; inline void getTokenStartPosition(int index, int *line, int *column) const { positionAt((m_firstToken + index)->position, line, column); } inline void getTokenEndPosition(int index, int *line, int *column) const { Token *tk = m_firstToken + index; positionAt(tk->position + tk->length, line, column); } inline void rewind(int index) { m_currentToken = m_firstToken + index; } inline int cursor() const { return m_currentToken - m_firstToken; } inline void nextToken() { m_currentToken++; m_cursor++; } //private: QByteArray m_contents; QVector m_lines; int m_line; QVector m_tokens; int m_tokensCount; Token *m_firstToken; Token *m_currentToken; int m_cursor; }; class MILexer { public: MILexer(); ~MILexer(); TokenStream *tokenize(const FileSymbol *fileSymbol); private: int nextToken(int &position, int &len); void scanChar(int *kind); void scanUnicodeChar(int *kind); void scanNewline(int *kind); void scanWhiteSpaces(int *kind); void scanStringLiteral(int *kind); void scanNumberLiteral(int *kind); void scanIdentifier(int *kind); void setupScanTable(); private: static bool s_initialized; static scan_fun_ptr s_scan_table[128 + 1]; QByteArray m_contents; int m_ptr = 0; // Cached 'm_contents.length()' int m_length = 0; QVector m_lines; int m_line = 0; QVector m_tokens; int m_tokensCount = 0; int m_cursor = 0; }; inline FileSymbol::~FileSymbol() { delete tokenStream; tokenStream = nullptr; } } // end of MI } // end of KDevMI Q_DECLARE_TYPEINFO(KDevMI::MI::Token, Q_PRIMITIVE_TYPE); #endif diff --git a/plugins/debuggercommon/mibreakpointcontroller.h b/plugins/debuggercommon/mibreakpointcontroller.h index 07ed084fdf..d7be1118a3 100644 --- a/plugins/debuggercommon/mibreakpointcontroller.h +++ b/plugins/debuggercommon/mibreakpointcontroller.h @@ -1,116 +1,116 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MIBREAKPOINTCONTROLLER_H #define MIBREAKPOINTCONTROLLER_H #include "dbgglobal.h" #include #include namespace KDevMI { namespace MI { struct AsyncRecord; struct ResultRecord; struct Value; } struct BreakpointData { int debuggerId = -1; KDevelop::BreakpointModel::ColumnFlags dirty; KDevelop::BreakpointModel::ColumnFlags sent; KDevelop::BreakpointModel::ColumnFlags errors; bool pending = false; BreakpointData() {} }; -typedef QSharedPointer BreakpointDataPtr; +using BreakpointDataPtr = QSharedPointer; class MIDebugSession; /** * Handles signals from the editor that relate to breakpoints and the execution * point of the debugger. * We may change, add or remove breakpoints in this class. */ class MIBreakpointController : public KDevelop::IBreakpointController { Q_OBJECT public: explicit MIBreakpointController(MIDebugSession* parent); using IBreakpointController::breakpointModel; /** * Controls whether when duplicate breakpoints are received via async notification from GDB, * one of the duplicates will be deleted. */ void setDeleteDuplicateBreakpoints(bool enable); void breakpointAdded(int row) override; void breakpointModelChanged(int row, KDevelop::BreakpointModel::ColumnFlags columns) override; void breakpointAboutToBeDeleted(int row) override; void debuggerStateChanged(KDevelop::IDebugSession::DebuggerState) override; void notifyBreakpointCreated(const MI::AsyncRecord& r); void notifyBreakpointModified(const MI::AsyncRecord& r); void notifyBreakpointDeleted(const MI::AsyncRecord& r); public Q_SLOTS: void initSendBreakpoints(); private Q_SLOTS: void programStopped(const MI::AsyncRecord &r); private: MIDebugSession* debugSession() const; int breakpointRow(const BreakpointDataPtr& breakpoint); void createBreakpoint(int row); void sendUpdates(int row); void recalculateState(int row); void sendMaybe(KDevelop::Breakpoint *breakpoint) override; void createFromDebugger(const MI::Value& miBkpt); void updateFromDebugger(int row, const MI::Value& miBkpt, KDevelop::BreakpointModel::ColumnFlags lockedColumns = {}); int rowFromDebuggerId(int gdbId) const; struct Handler; struct InsertedHandler; struct UpdateHandler; struct DeleteHandler; struct IgnoreChanges; QList m_breakpoints; QList m_pendingDeleted; int m_ignoreChanges = 0; bool m_deleteDuplicateBreakpoints = false; }; } // end of namespace KDevMI #endif // MIBREAKPOINTCONTROLLER_H diff --git a/plugins/documentview/settings/preferences.cpp b/plugins/documentview/settings/preferences.cpp index e47cedcc19..42ec32d6ef 100644 --- a/plugins/documentview/settings/preferences.cpp +++ b/plugins/documentview/settings/preferences.cpp @@ -1,70 +1,70 @@ /* Document View Settings * * Copyright 2006 Matt Rogers * * 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 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 "preferences.h" #include #include "ui_settingswidget.h" #include -typedef KGenericFactory PreferencesFactory; +using PreferencesFactory = KGenericFactory; K_EXPORT_COMPONENT_FACTORY( kcm_documentview_settings, PreferencesFactory( "kcm_documentview_settings" ) ) Preferences::Preferences(QWidget *parent, const QStringList &args) : KDevelop::ConfigModule( DocumentViewSettings::self(), PreferencesFactory::componentData(), parent, args ) { QVBoxLayout* l = new QVBoxLayout( this ); QWidget* w = new QWidget; preferencesDialog = new Ui::SettingsWidget; preferencesDialog->setupUi( w ); l->addWidget( w ); addConfig( DocumentViewSettings::self(), w ); load(); } Preferences::~Preferences( ) { delete preferencesDialog; } void Preferences::save() { KDevelop::ConfigModule::save(); } void Preferences::load() { KDevelop::ConfigModule::load(); } void Preferences::slotSettingsChanged() { emit changed( true ); } void Preferences::defaults() { } diff --git a/plugins/filetemplates/licensepage.cpp b/plugins/filetemplates/licensepage.cpp index 23dcc6e4b7..f709251513 100644 --- a/plugins/filetemplates/licensepage.cpp +++ b/plugins/filetemplates/licensepage.cpp @@ -1,289 +1,289 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda 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 "licensepage.h" #include "ui_licensechooser.h" #include "debug.h" #include #include #include #include #include #include #include namespace KDevelop { struct LicensePagePrivate { struct LicenseInfo { QString name; QString path; QString contents; bool operator< (const LicenseInfo& o) const { return name.localeAwareCompare(o.name) < 0; } }; - typedef QVector LicenseList; + using LicenseList = QVector; explicit LicensePagePrivate(LicensePage* page_) : license(nullptr) , page(page_) { } // methods void initializeLicenses(); QString readLicense(int licenseIndex); bool saveLicense(); // slots void licenseComboChanged(int license); Ui::LicenseChooserDialog* license; LicenseList availableLicenses; LicensePage* page; }; //! Read all the license files in the global and local config dirs void LicensePagePrivate::initializeLicenses() { qCDebug(PLUGIN_FILETEMPLATES) << "Searching for available licenses"; const QStringList licenseDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevcodegen/licenses"), QStandardPaths::LocateDirectory); //Iterate through the possible directories that contain licenses, and load their names for (const QString& currentDir : licenseDirs) { QDirIterator it(currentDir, QDir::Files | QDir::Readable); while(it.hasNext()) { LicenseInfo newLicense; newLicense.path = it.next(); newLicense.name = it.fileName(); qCDebug(PLUGIN_FILETEMPLATES) << "Found License: " << newLicense.name; availableLicenses.push_back(newLicense); } } std::sort(availableLicenses.begin(), availableLicenses.end()); foreach(const LicenseInfo& info, availableLicenses) { license->licenseComboBox->addItem(info.name); } //Finally add the option other for user specified licenses LicenseInfo otherLicense; availableLicenses.push_back(otherLicense); license->licenseComboBox->addItem(i18n("Other")); } // Read a license index, if it is not loaded, open it from the file QString LicensePagePrivate::readLicense(int licenseIndex) { //If the license is not loaded into memory, read it in if(availableLicenses[licenseIndex].contents.isEmpty()) { QString licenseText; //If we are dealing with the last option "other" just return a new empty string if(licenseIndex != (availableLicenses.size() - 1)) { qCDebug(PLUGIN_FILETEMPLATES) << "Reading license: " << availableLicenses[licenseIndex].name ; QFile newLicense(availableLicenses[licenseIndex].path); if(newLicense.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream newLicenseText(&newLicense); newLicenseText.setAutoDetectUnicode(true); licenseText = newLicenseText.readAll(); newLicense.close(); // license text files are stored with a trailing linebreak, // as otherwise some tools complain about it // so deal with that and remove any potential trailing linebreak // which otherwise would result in a trailing empty line QRegularExpression anyLinebreakAtEnd(QStringLiteral("(\n|\r\n)$")); licenseText.remove(anyLinebreakAtEnd); } else licenseText = QStringLiteral("Error, could not open license file.\n Was it deleted?"); } availableLicenses[licenseIndex].contents = licenseText; } return availableLicenses[licenseIndex].contents; } // ---Slots--- void LicensePagePrivate::licenseComboChanged(int selectedLicense) { //If the last slot is selected enable the save license combobox if(selectedLicense == (availableLicenses.size() - 1)) { license->licenseTextEdit->clear(); license->licenseTextEdit->setReadOnly(false); license->saveLicense->setEnabled(true); } else { license->saveLicense->setEnabled(false); license->licenseTextEdit->setReadOnly(true); } if(selectedLicense < 0 || selectedLicense >= availableLicenses.size()) license->licenseTextEdit->setText(i18n("Could not load previous license")); else license->licenseTextEdit->setText(readLicense(selectedLicense)); } bool LicensePagePrivate::saveLicense() { qCDebug(PLUGIN_FILETEMPLATES) << "Attempting to save custom license: " << license->licenseName->text(); QString localDataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+QLatin1String("/kdevcodegen/licenses/"); QString fullPath = localDataDir + license->licenseName->text(); QFile newFile(fullPath); if(newFile.exists()) { KMessageBox::sorry(page, i18n("The specified license already exists. " "Please provide a different name.")); return false; } QDir().mkpath(localDataDir); newFile.open(QIODevice::WriteOnly); qint64 result = newFile.write(license->licenseTextEdit->toPlainText().toUtf8()); newFile.close(); if(result == -1) { KMessageBox::sorry(page, i18n("Failed to write custom license template to file %1.", fullPath)); return false; } // also add to our data structures, this esp. needed for proper saving // of the license index so it can be restored the next time we show up LicenseInfo info; info.name = license->licenseName->text(); info.path = localDataDir; availableLicenses << info; // find index of the new the license, omitting the very last item ('Other') int idx = availableLicenses.count() - 1; for(int i = 0; i < availableLicenses.size() - 1; ++i) { if (info < availableLicenses.at(i)) { idx = i; break; } } availableLicenses.insert(idx, info); license->licenseComboBox->insertItem(idx, info.name); license->licenseComboBox->setCurrentIndex(idx); return true; } LicensePage::LicensePage(QWidget* parent) : QWidget(parent) , d(new LicensePagePrivate(this)) { d->license = new Ui::LicenseChooserDialog; d->license->setupUi(this); connect(d->license->licenseComboBox, static_cast(&KComboBox::currentIndexChanged), this, [&] (int selectedLicense) { d->licenseComboChanged(selectedLicense); }); connect(d->license->saveLicense, &QCheckBox::clicked, d->license->licenseName, &QLineEdit::setEnabled); // Read all the available licenses from the standard dirs d->initializeLicenses(); //Set the license selection to the previous one KConfigGroup config(KSharedConfig::openConfig()->group("CodeGeneration")); d->license->licenseComboBox->setCurrentIndex(config.readEntry( "LastSelectedLicense", 0 )); // Needed to avoid a bug where licenseComboChanged doesn't get // called by QComboBox if the past selection was 0 d->licenseComboChanged(d->license->licenseComboBox->currentIndex()); } LicensePage::~LicensePage() { if (d->license->saveLicense->isChecked() && !d->license->licenseName->text().isEmpty()) { d->saveLicense(); } KConfigGroup config(KSharedConfig::openConfig()->group("CodeGeneration")); //Do not save invalid license numbers' int index = d->license->licenseComboBox->currentIndex(); if( index >= 0 || index < d->availableLicenses.size() ) { config.writeEntry("LastSelectedLicense", index); config.config()->sync(); } else { qCWarning(PLUGIN_FILETEMPLATES) << "Attempted to save an invalid license number: " << index << ". Number of licenses:" << d->availableLicenses.size(); } delete d->license; delete d; } QString LicensePage::license() const { QString licenseText = d->license->licenseTextEdit->document()->toPlainText(); /* Add date, name and email to license text */ licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("yyyy"))); licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("MM"))); licenseText.replace(QLatin1String(""), QDate::currentDate().toString(QStringLiteral("dd"))); QString developer(QStringLiteral("%1 <%2>")); KEMailSettings emailSettings; QString name = emailSettings.getSetting(KEMailSettings::RealName); if (name.isEmpty()) { name = QStringLiteral(""); } developer = developer.arg(name); QString email = emailSettings.getSetting(KEMailSettings::EmailAddress); if (email.isEmpty()) { email = QStringLiteral("email"); //no < > as they are already through the email field } developer = developer.arg(email); licenseText.replace(QLatin1String(""), developer); return licenseText; } void LicensePage::setFocusToFirstEditWidget() { d->license->licenseComboBox->setFocus(); } } Q_DECLARE_TYPEINFO(KDevelop::LicensePagePrivate::LicenseInfo, Q_MOVABLE_TYPE); #include "moc_licensepage.cpp" diff --git a/plugins/grepview/grepoutputmodel.h b/plugins/grepview/grepoutputmodel.h index 34185d6354..6c0d6a8b78 100644 --- a/plugins/grepview/grepoutputmodel.h +++ b/plugins/grepview/grepoutputmodel.h @@ -1,112 +1,112 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Silvère Lestang * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPOUTPUTMODEL_H #define KDEVPLATFORM_PLUGIN_GREPOUTPUTMODEL_H #include #include #include class QModelIndex; class QRegExp; namespace KDevelop { class IStatus; } class GrepOutputItem : public QStandardItem { public: - typedef QList List; + using List = QList; GrepOutputItem(const KDevelop::DocumentChangePointer& change, const QString& text, bool checkable); GrepOutputItem(const QString &filename, const QString &text, bool checkable); ~GrepOutputItem() override; QString filename() const ; int lineNumber() const ; KDevelop::DocumentChangePointer change() const ; bool isText() const ; /// Recursively apply check state to children void propagateState() ; /// Check children to determine current state void refreshState() ; QVariant data ( int role = Qt::UserRole + 1 ) const override; private: KDevelop::DocumentChangePointer m_change; }; Q_DECLARE_METATYPE(GrepOutputItem::List) class GrepOutputModel : public QStandardItemModel { Q_OBJECT public: explicit GrepOutputModel( QObject *parent = nullptr ); ~GrepOutputModel() override; void setRegExp(const QRegExp& re); void setReplacementTemplate(const QString &tmpl); /// applies replacement on given text QString replacementFor(const QString &text); void clear(); // resets file & match counts bool hasResults(); QModelIndex previousItemIndex(const QModelIndex ¤tIdx) const; QModelIndex nextItemIndex(const QModelIndex ¤tIdx) const; const GrepOutputItem *getRootItem() const; void makeItemsCheckable(bool checkable); bool itemsCheckable() const; public Q_SLOTS: void appendOutputs( const QString &filename, const GrepOutputItem::List &lines ); void activate( const QModelIndex &idx ); void doReplacements(); void setReplacement(const QString &repl); //receive status message from GrepJob, and store it void showMessageSlot( KDevelop::IStatus*, const QString& message ); //emit stored message as signal 'showMessage' to GrepOutputView. //called when user selects a search with the combobox void showMessageEmit(); Q_SIGNALS: void showMessage( KDevelop::IStatus*, const QString& message ); void showErrorMessage(const QString & message, int timeout = 0); private: void makeItemsCheckable(bool checkable, GrepOutputItem* item); QRegExp m_regExp; QString m_replacement; QString m_replacementTemplate; QString m_finalReplacement; bool m_finalUpToDate = false; /// says if m_finalReplacement is up to date or must be regenerated GrepOutputItem *m_rootItem = nullptr; int m_fileCount = 0; int m_matchCount = 0; QString m_savedMessage; KDevelop::IStatus *m_savedIStatus; bool m_itemsCheckable = false; private Q_SLOTS: void updateCheckState(QStandardItem*); }; #endif diff --git a/plugins/grepview/tests/test_findreplace.h b/plugins/grepview/tests/test_findreplace.h index b07fe047c7..79de54b0fd 100644 --- a/plugins/grepview/tests/test_findreplace.h +++ b/plugins/grepview/tests/test_findreplace.h @@ -1,61 +1,61 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_REPLACETEST_H #define KDEVPLATFORM_PLUGIN_REPLACETEST_H #include #include #include #include namespace KDevelop { class TestCore; } class GrepViewPlugin; class FindReplaceTest : public QObject { Q_OBJECT public: struct Match { Match() {} Match(int l,int s,int e) : line(l), start(s), end(e) {} int line; int start; int end; }; - typedef QList MatchList; + using MatchList = QList; - typedef QPair File; /// Represent a file with name => content - typedef QList FileList; + using File = QPair; /// Represent a file with name => content + using FileList = QList; private: GrepViewPlugin* m_plugin; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testFind(); void testFind_data(); void testReplace(); void testReplace_data(); }; Q_DECLARE_METATYPE(FindReplaceTest::MatchList) Q_DECLARE_METATYPE(FindReplaceTest::FileList) #endif // KDEVPLATFORM_PLUGIN_REPLACETEST_H diff --git a/plugins/makebuilder/imakebuilder.h b/plugins/makebuilder/imakebuilder.h index 9a07113222..2473a336fa 100644 --- a/plugins/makebuilder/imakebuilder.h +++ b/plugins/makebuilder/imakebuilder.h @@ -1,94 +1,94 @@ /* KDevelop * * Copyright 2007 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 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 IMAKEBUILDER_H #define IMAKEBUILDER_H #include #include #include #include class KJob; /** @author Andreas Pakulat */ /** * Used to create make variables of the form KEY=VALUE. */ -typedef QVector> MakeVariables; +using MakeVariables = QVector>; class IMakeBuilder : public KDevelop::IProjectBuilder { public: ~IMakeBuilder() override = default; /** * Return a build job for the given target. * * E.g. if @a targetnames is \c 'myModule', the returned job * will execute the command: * * \code make myModule \endcode * * The command is executed inside the directory of @a item and uses the * configuration of its project. * * @param item Item of the project to build. * @param targetname Command-line target name to pass to make. */ virtual KJob* executeMakeTarget(KDevelop::ProjectBaseItem* item, const QString& targetname ) = 0; /** * Return a build job for the given targets with optional make variables defined. * * E.g. if @a targetnames is \code {'all', 'modules'} \endcode and @a variables is * \code { 'CFLAGS' : '-Wall', 'CC' : 'gcc-3.4' } \endcode, the returned job should * execute this command: * * \code make CFLAGS=-Wall CC=gcc-3.4 all modules \endcode * * The command is executed inside the directory of @a item and uses the * configuration of its project. * * @param item Item of the project to build. * @param targetnames Optional command-line targets names to pass to make. * @param variables Optional list of command-line variables to pass to make. */ virtual KJob* executeMakeTargets(KDevelop::ProjectBaseItem* item, const QStringList& targetnames = QStringList(), const MakeVariables& variables = MakeVariables() ) = 0; Q_SIGNALS: /** * Emitted every time a target is finished being built for a project item. */ void makeTargetBuilt( KDevelop::ProjectBaseItem* item, const QString& targetname ); }; Q_DECLARE_INTERFACE( IMakeBuilder, "org.kdevelop.IMakeBuilder" ) #endif diff --git a/plugins/manpage/manpagemodel.h b/plugins/manpage/manpagemodel.h index 67799d8161..c6223b85a6 100644 --- a/plugins/manpage/manpagemodel.h +++ b/plugins/manpage/manpagemodel.h @@ -1,98 +1,98 @@ /* This file is part of KDevelop Copyright 2010 Yannick Motta Copyright 2010 Benjamin Port Copyright 2014 Milian Wolff 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 MANPAGEMODEL_H #define MANPAGEMODEL_H #include #include #include class QStringListModel; // id and name for man section -typedef QPair ManSection; +using ManSection = QPair; class ManPageModel : public QAbstractItemModel { Q_OBJECT public: explicit ManPageModel(QObject* parent = nullptr); ~ManPageModel() override; /** * You can use @p DeclarationRole to get the Declaration for a given index. * NOTE: If you use that, don't forget to lock the DUChain if you access the declaration! */ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; } QModelIndex parent(const QModelIndex& child = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QStringListModel* indexList(); bool containsIdentifier(const QString& identifier); int sectionCount() const; bool isLoaded() const; int nbSectionLoaded() const; bool identifierInSection(const QString &identifier, const QString §ion) const; bool hasError() const; const QString& errorString() const; Q_SIGNALS: void sectionParsed(); void sectionListUpdated(); void manPagesLoaded(); void error(const QString& errorString); public Q_SLOTS: void showItem(const QModelIndex& idx); void showItemFromUrl(const QUrl& url); private Q_SLOTS: void initModel(); void indexEntries(KIO::Job* job, const KIO::UDSEntryList& entries); void indexLoaded(KJob* job); void sectionEntries(KIO::Job* job, const KIO::UDSEntryList& entries); void sectionLoaded(); private: QString manPage(const QString §ionUrl, int position) const; void initSection(); QListIterator *iterator = nullptr; QList m_sectionList; QHash > m_manMap; QStringList m_index; QStringListModel* m_indexModel; bool m_loaded = false; int m_nbSectionLoaded = 0; QString m_errorString; }; #endif // MANPAGEMODEL_H diff --git a/plugins/patchreview/patchreview.h b/plugins/patchreview/patchreview.h index d8374bfac1..0f7e43d54d 100644 --- a/plugins/patchreview/patchreview.h +++ b/plugins/patchreview/patchreview.h @@ -1,141 +1,141 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_PATCHREVIEW_H #define KDEVPLATFORM_PLUGIN_PATCHREVIEW_H #include #include #include #include class PatchHighlighter; class PatchReviewToolViewFactory; class QTimer; namespace KDevelop { class IDocument; } namespace Sublime { class Area; } namespace Diff2 { class KompareModelList; class DiffModel; } namespace Kompare { struct Info; } class DiffSettings; class PatchReviewPlugin; class PatchReviewPlugin : public KDevelop::IPlugin, public KDevelop::IPatchReview, public KDevelop::ILanguageSupport { Q_OBJECT Q_INTERFACES( KDevelop::IPatchReview ) Q_INTERFACES( KDevelop::ILanguageSupport ) public : explicit PatchReviewPlugin( QObject *parent, const QVariantList & = QVariantList() ); ~PatchReviewPlugin() override; void unload() override; KDevelop::IPatchSource::Ptr patch() const { return m_patch; } Diff2::KompareModelList* modelList() const { return m_modelList.data(); } QString name() const override { return QStringLiteral("diff"); } KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &) override { return nullptr; } void seekHunk( bool forwards, const QUrl& file = QUrl() ); void setPatch( KDevelop::IPatchSource* patch ); void startReview( KDevelop::IPatchSource* patch, ReviewMode mode ) override; void finishReview(const QList& selection); QUrl urlForFileModel( const Diff2::DiffModel* model ); QAction* finishReviewAction() const { return m_finishReview; } KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; Q_SIGNALS: void startingNewReview(); void patchChanged(); public Q_SLOTS : //Does parts of the review-starting that are problematic to do directly in startReview, as they may open dialogs etc. void updateReview(); void cancelReview(); void clearPatch( QObject* patch ); void notifyPatchChanged(); void highlightPatch(); void updateKompareModel(); void forceUpdate(); void areaChanged(Sublime::Area* area); void executeFileReviewAction(); private Q_SLOTS : void documentClosed( KDevelop::IDocument* ); void textDocumentCreated( KDevelop::IDocument* ); void documentSaved( KDevelop::IDocument* ); void closeReview(); private: void switchToEmptyReviewArea(); /// Makes sure that this working set is active only in the @p area, and that its name starts with "review". void setUniqueEmptyWorkingSet(Sublime::Area* area); void addHighlighting( const QUrl& file, KDevelop::IDocument* document = nullptr ); void removeHighlighting( const QUrl& file = QUrl() ); KDevelop::IPatchSource::Ptr m_patch; QTimer* m_updateKompareTimer; PatchReviewToolViewFactory* m_factory; QAction* m_finishReview; #if 0 void determineState(); #endif QPointer< DiffSettings > m_diffSettings; QScopedPointer< Kompare::Info > m_kompareInfo; QScopedPointer< Diff2::KompareModelList > m_modelList; uint m_depth = 0; // depth of the patch represented by m_modelList - typedef QMap< QUrl, QPointer< PatchHighlighter > > HighlightMap; + using HighlightMap = QMap>; HighlightMap m_highlighters; friend class PatchReviewToolView; // to access slot exporterSelected(); }; #endif diff --git a/plugins/projectfilter/filter.h b/plugins/projectfilter/filter.h index 81c2f12126..5d2194f234 100644 --- a/plugins/projectfilter/filter.h +++ b/plugins/projectfilter/filter.h @@ -1,96 +1,96 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #ifndef FILTER_H #define FILTER_H #include #include #include namespace KDevelop { struct SerializedFilter; /** * The Filter is a the class which is used for actual matching against items. * * It "compiles" serialized filters for performance and extracts useful information. */ struct Filter { public: enum Target { Files = 1, Folders = 2 }; Q_DECLARE_FLAGS(Targets, Target) enum Type { /// Hides matched targets. Exclusive, /// Reverses the match to be inclusive and negates the previously applied exclusive filters. Inclusive }; Filter(); Filter(const SerializedFilter& filter); bool operator==(const Filter& filter) const { return filter.pattern == pattern && filter.targets == targets && filter.type == type; } QRegExp pattern; Targets targets; Type type = Exclusive; }; -typedef QVector Filters; +using Filters = QVector; /** * SerializedFilter is what gets stored on disk in the configuration and represents * the interface which the user can interact with. */ struct SerializedFilter { SerializedFilter(); SerializedFilter(const QString& pattern, Filter::Targets targets, Filter::Type type = Filter::Exclusive); QString pattern; Filter::Targets targets; Filter::Type type = Filter::Exclusive; }; -typedef QVector SerializedFilters; +using SerializedFilters = QVector; SerializedFilters defaultFilters(); SerializedFilters readFilters(const KSharedConfigPtr& config); void writeFilters(const SerializedFilters& filters, KSharedConfigPtr config); Filters deserialize(const SerializedFilters& filters); } Q_DECLARE_TYPEINFO(KDevelop::Filter, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::SerializedFilter, Q_MOVABLE_TYPE); #endif // FILTER_H diff --git a/plugins/projectfilter/tests/test_projectfilter.cpp b/plugins/projectfilter/tests/test_projectfilter.cpp index b011729881..d5e6422161 100644 --- a/plugins/projectfilter/tests/test_projectfilter.cpp +++ b/plugins/projectfilter/tests/test_projectfilter.cpp @@ -1,398 +1,398 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "test_projectfilter.h" #include #include #include #include #include #include "../projectfilter.h" QTEST_GUILESS_MAIN(TestProjectFilter) using namespace KDevelop; -typedef QSharedPointer TestFilter; +using TestFilter = QSharedPointer; Q_DECLARE_METATYPE(TestFilter) namespace { const bool Invalid = false; const bool Valid = true; const bool Folder = true; const bool File = false; struct MatchTest { QString path; bool isFolder; bool shouldMatch; }; void addTests(const QString& tag, const TestProject& project, const TestFilter& filter, MatchTest* tests, uint numTests) { for (uint i = 0; i < numTests; ++i) { const MatchTest& test = tests[i]; QTest::newRow(qstrdup(qPrintable(tag + ':' + test.path))) << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch; if (test.isFolder) { // also test folder with trailing slash - should not make a difference QTest::newRow(qstrdup(qPrintable(tag + ':' + test.path + '/'))) << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch; } } } ///FIXME: remove once we can use c++11 #define ADD_TESTS(tag, project, filter, tests) addTests(QStringLiteral(tag), project, filter, tests, sizeof(tests) / sizeof(tests[0])) struct BenchData { BenchData(const Path &path = Path(), bool isFolder = false) : path(path) , isFolder(isFolder) {} Path path; bool isFolder; }; } Q_DECLARE_METATYPE(QVector) void TestProjectFilter::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); } void TestProjectFilter::cleanupTestCase() { TestCore::shutdown(); } void TestProjectFilter::match() { QFETCH(TestFilter, filter); QFETCH(KDevelop::Path, path); QFETCH(bool, isFolder); QFETCH(bool, expectedIsValid); QCOMPARE(filter->isValid(path, isFolder), expectedIsValid); } void TestProjectFilter::match_data() { QTest::addColumn("filter"); QTest::addColumn("path"); QTest::addColumn("isFolder"); QTest::addColumn("expectedIsValid"); { // test default filters const TestProject project; TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters()))); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("folder/folder"), Folder, Valid}, {QStringLiteral("file"), File, Valid}, {QStringLiteral("folder/file"), File, Valid}, {QStringLiteral(".file"), File, Invalid}, {QStringLiteral(".folder"), Folder, Invalid}, {QStringLiteral("folder/.folder"), Folder, Invalid}, {QStringLiteral("folder/.file"), File, Invalid}, {QStringLiteral(".git"), Folder, Invalid}, {QStringLiteral(".gitignore"), File, Valid}, {QStringLiteral(".gitmodules"), File, Valid}, {QStringLiteral("_darcs"), Folder, Invalid}, {QStringLiteral("_svn"), Folder, Invalid}, {QStringLiteral(".svn"), Folder, Invalid}, {QStringLiteral("CVS"), Folder, Invalid}, {QStringLiteral("SCCS"), Folder, Invalid}, {QStringLiteral(".hg"), Folder, Invalid}, {QStringLiteral(".bzr"), Folder, Invalid}, {QStringLiteral("foo.o"), File, Invalid}, {QStringLiteral("foo.so"), File, Invalid}, {QStringLiteral("foo.so.1"), File, Invalid}, {QStringLiteral("foo.a"), File, Invalid}, {QStringLiteral("moc_foo.cpp"), File, Invalid}, {QStringLiteral("ui_foo.h"), File, Invalid}, {QStringLiteral("qrc_foo.cpp"), File, Invalid}, {QStringLiteral("foo.cpp~"), File, Invalid}, {QStringLiteral(".foo.cpp.kate-swp"), File, Invalid}, {QStringLiteral(".foo.cpp.swp"), File, Invalid} }; ADD_TESTS("default", project, filter, tests); } { // test exclude files, basename const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Valid}, {QStringLiteral("file.cpp"), File, Invalid}, {QStringLiteral("folder.cpp"), Folder, Valid}, {QStringLiteral("folder/file.cpp"), File, Invalid}, {QStringLiteral("folder/folder.cpp"), Folder, Valid} }; ADD_TESTS("exclude:*.cpp", project, filter, tests); } { // test excludes on folders const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("foo"), Filter::Folders)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Valid}, {QStringLiteral("foo"), Folder, Invalid}, {QStringLiteral("folder/file"), File, Valid}, {QStringLiteral("folder/foo"), Folder, Invalid}, {QStringLiteral("folder/foo"), File, Valid} }; ADD_TESTS("exclude:foo", project, filter, tests); } { // test includes const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files)) << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files, Filter::Inclusive)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Invalid}, {QStringLiteral("file.cpp"), File, Valid}, {QStringLiteral(".file.cpp"), File, Valid}, {QStringLiteral("folder/file.cpp"), File, Valid}, {QStringLiteral("folder/.file.cpp"), File, Valid} }; ADD_TESTS("include:*.cpp", project, filter, tests); project.projectConfiguration(); } { // test mixed stuff const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files, Filter::Exclusive)) << Filter(SerializedFilter(QStringLiteral("*.inc"), Filter::Files, Filter::Inclusive)) << Filter(SerializedFilter(QStringLiteral("*ex.inc"), Filter::Files, Filter::Exclusive)) << Filter(SerializedFilter(QStringLiteral("bar"), Filter::Folders, Filter::Exclusive)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Invalid}, {QStringLiteral("file.inc"), File, Valid}, {QStringLiteral("file.ex.inc"), File, Invalid}, {QStringLiteral("folder/file"), File, Invalid}, {QStringLiteral("folder/file.inc"), File, Valid}, {QStringLiteral("folder/file.ex.inc"), File, Invalid}, {QStringLiteral("bar"), Folder, Invalid}, }; ADD_TESTS("mixed", project, filter, tests); } { // relative path const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("/foo/*bar"), Filter::Targets(Filter::Files | Filter::Folders))); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("foo"), Folder, Valid}, {QStringLiteral("bar"), File, Valid}, {QStringLiteral("foo/bar"), Folder, Invalid}, {QStringLiteral("foo/bar"), File, Invalid}, {QStringLiteral("foo/asdf/bar"), Folder, Invalid}, {QStringLiteral("foo/asdf/bar"), File, Invalid}, {QStringLiteral("foo/asdf_bar"), Folder, Invalid}, {QStringLiteral("foo/asdf_bar"), File, Invalid}, {QStringLiteral("asdf/bar"), File, Valid}, {QStringLiteral("asdf/foo/bar"), File, Valid}, }; ADD_TESTS("relative", project, filter, tests); } { // trailing slash const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("bar/"), Filter::Targets(Filter::Files | Filter::Folders))); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("foo"), Folder, Valid}, {QStringLiteral("bar"), File, Valid}, {QStringLiteral("bar"), Folder, Invalid}, {QStringLiteral("foo/bar"), File, Valid}, {QStringLiteral("foo/bar"), Folder, Invalid} }; ADD_TESTS("trailingslash", project, filter, tests); } { // escaping const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("foo\\*bar"), Filter::Files)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("foobar"), Folder, Valid}, {QStringLiteral("fooasdfbar"), File, Valid}, {QStringLiteral("foo*bar"), File, Invalid}, {QStringLiteral("foo/bar"), Folder, Valid} }; ADD_TESTS("escaping", project, filter, tests); } } static QVector createBenchData(const Path& base, int folderDepth, int foldersPerFolder, int filesPerFolder) { QVector data; data << BenchData(base, true); for(int i = 0; i < filesPerFolder; ++i) { if (i % 2) { data << BenchData(Path(base, QStringLiteral("file%1.cpp").arg(i)), false); } else { data << BenchData(Path(base, QStringLiteral("file%1.h").arg(i)), true); } } for(int i = 0; i < foldersPerFolder && folderDepth > 0; ++i) { data += createBenchData(Path(base, QStringLiteral("folder%1").arg(i)), folderDepth - 1, foldersPerFolder, filesPerFolder); } return data; } void TestProjectFilter::bench() { QFETCH(TestFilter, filter); QFETCH(QVector, data); QBENCHMARK { foreach(const BenchData& bench, data) { filter->isValid(bench.path, bench.isFolder); } } } void TestProjectFilter::bench_data() { QTest::addColumn("filter"); QTest::addColumn >("data"); const TestProject project; const QVector > dataSets = QVector >() << createBenchData(project.path(), 3, 5, 10) << createBenchData(project.path(), 3, 5, 20) << createBenchData(project.path(), 4, 5, 10) << createBenchData(project.path(), 3, 10, 10); { TestFilter filter(new ProjectFilter(&project, Filters())); for (const QVector& data : dataSets) { QTest::newRow(QByteArray("baseline-" + QByteArray::number(data.size()))) << filter << data; } } { TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters()))); for (const QVector& data : dataSets) { QTest::newRow(QByteArray("defaults-" + QByteArray::number(data.size()))) << filter << data; } } } diff --git a/plugins/qmakemanager/parser/qmakeastvisitor.h b/plugins/qmakemanager/parser/qmakeastvisitor.h index f6a1cfb39f..4a91778c1b 100644 --- a/plugins/qmakemanager/parser/qmakeastvisitor.h +++ b/plugins/qmakemanager/parser/qmakeastvisitor.h @@ -1,57 +1,57 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 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 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 QMAKEASTVISITOR_H #define QMAKEASTVISITOR_H #include "parser_export.h" namespace QMake { class AST; class ProjectAST; class AssignmentAST; class FunctionCallAST; class SimpleScopeAST; class OrAST; class ScopeBodyAST; class ValueAST; class KDEVQMAKEPARSER_EXPORT ASTVisitor { - typedef void (ASTVisitor::*parser_fun_t)(AST*); + using parser_fun_t = void (ASTVisitor::*)(AST*); static parser_fun_t _S_parser_table[]; public: virtual ~ASTVisitor(); virtual void visitNode( AST* node ); virtual void visitProject( ProjectAST* node ); virtual void visitAssignment( AssignmentAST* node ); virtual void visitValue( ValueAST* node ); virtual void visitFunctionCall( FunctionCallAST* node ); virtual void visitScopeBody( ScopeBodyAST* node ); virtual void visitOr( OrAST* node ); virtual void visitSimpleScope( SimpleScopeAST* node ); }; } #endif diff --git a/plugins/qmakemanager/qmakefilevisitor.h b/plugins/qmakemanager/qmakefilevisitor.h index a63f5bfecd..baf1f50dd8 100644 --- a/plugins/qmakemanager/qmakefilevisitor.h +++ b/plugins/qmakemanager/qmakefilevisitor.h @@ -1,71 +1,71 @@ /***************************************************************************** * Copyright (c) 2010 Milian Wolff * * * * 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 General Public License * * along with this program. If not, see . * *****************************************************************************/ #ifndef QMAKEFILEVISITOR_H #define QMAKEFILEVISITOR_H #include "parser/qmakeastdefaultvisitor.h" #include "variablereferenceparser.h" #include class QMakeVariableResolver { public: virtual ~QMakeVariableResolver() {} virtual QStringList resolveVariable(const QString& variable, VariableInfo::VariableType type) const = 0; - typedef QHash< QString, QStringList > VariableMap; + using VariableMap = QHash; }; class QMakeFile; class QMakeFileVisitor : protected QMake::ASTDefaultVisitor, public QMakeVariableResolver { public: explicit QMakeFileVisitor(const QMakeVariableResolver* resolver, QMakeFile* baseFile); ~QMakeFileVisitor() override; QStringList resolveVariable(const QString& variable, VariableInfo::VariableType type) const override; // use this before visiting a file to set the default variables void setVariables(const VariableMap& vars); // visit whole file and return map of defined variables VariableMap visitFile(QMake::ProjectAST* node); // visit macro with given arguments and forward its returnvalue QStringList visitMacro(QMake::ScopeBodyAST* node, const QStringList& arguments); protected: void visitAssignment(QMake::AssignmentAST* node) override; void visitFunctionCall(QMake::FunctionCallAST* node) override; private: QStringList resolveVariables( const QString& value ) const; QStringList getValueList( const QList& list ) const; QStringList evaluateMacro( const QString& function, const QStringList& arguments ) const; const QMakeVariableResolver* const m_resolver; QMakeFile* m_baseFile; VariableMap m_variableValues; QHash m_userMacros; QStringList m_arguments; QStringList m_lastReturn; }; #endif // QMAKEFILEVISITOR_H diff --git a/plugins/qmakemanager/qmakeprojectfile.h b/plugins/qmakemanager/qmakeprojectfile.h index 8d43c0a450..899a5dfd3a 100644 --- a/plugins/qmakemanager/qmakeprojectfile.h +++ b/plugins/qmakemanager/qmakeprojectfile.h @@ -1,90 +1,90 @@ /* KDevelop QMake Support * * 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 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 QMAKEPROJECTFILE_H #define QMAKEPROJECTFILE_H #include "qmakefile.h" class QMakeMkSpecs; template class QList; class QMakeMkSpecs; class QMakeCache; namespace KDevelop { class IProject; } class QMakeProjectFile : public QMakeFile { public: - typedef QPair< QString, QString > DefinePair; + using DefinePair = QPair; static const QStringList FileVariables; explicit QMakeProjectFile( const QString& projectfile ); ~QMakeProjectFile() override; bool read() override; QStringList subProjects() const; bool hasSubProject(const QString& file) const; QStringList files() const; QStringList filesForTarget( const QString& ) const; QStringList includeDirectories() const; QStringList frameworkDirectories() const; QStringList extraArguments() const; QStringList targets() const; QString getTemplate() const; void setMkSpecs( QMakeMkSpecs* mkspecs ); QMakeMkSpecs* mkSpecs() const; void setQMakeCache( QMakeCache* cache ); QMakeCache* qmakeCache() const; QStringList resolveVariable(const QString& variable, VariableInfo::VariableType type) const override; QList< DefinePair > defines() const; /// current pwd, e.g. absoluteDir even for included files virtual QString pwd() const; /// path to build dir for the current .pro file virtual QString outPwd() const; /// path to dir of current .pro file virtual QString proFilePwd() const; /// path to current .pro file virtual QString proFile() const; private: void addPathsForVariable(const QString& variable, QStringList* list, const QString& base = {}) const; QMakeMkSpecs* m_mkspecs; QMakeCache* m_cache; static QHash > m_qmakeQueryCache; QString m_qtIncludeDir; QString m_qtVersion; // On OS X, QT_INSTALL_LIBS is typically a framework directory and should thus be added to the framework search path QString m_qtLibDir; }; #endif diff --git a/plugins/qmakemanager/tests/test_qmakefile.cpp b/plugins/qmakemanager/tests/test_qmakefile.cpp index 3782ceac8a..c3db4adc0a 100644 --- a/plugins/qmakemanager/tests/test_qmakefile.cpp +++ b/plugins/qmakemanager/tests/test_qmakefile.cpp @@ -1,678 +1,678 @@ /* KDevelop QMake Support * * Copyright 2010 Milian Wolff * * 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 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 "test_qmakefile.h" #include "qmakefile.h" #include "variablereferenceparser.h" #include "qmakeprojectfile.h" #include "qmakemkspecs.h" #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(TestQMakeFile) -typedef QHash DefineHash; +using DefineHash = QHash; Q_DECLARE_METATYPE(QMakeFile::VariableMap) Q_DECLARE_METATYPE(DefineHash) namespace QTest { template <> char* toString(const QStringList& list) { QByteArray ba; if (list.isEmpty()) { ba = "()"; } else { ba = "(\"" + list.join(QStringLiteral("\", \"")).toLocal8Bit() + "\")"; } return qstrdup(ba.data()); } template <> char* toString(const QMakeFile::VariableMap& variables) { QByteArray ba = "VariableMap("; QMakeFile::VariableMap::const_iterator it = variables.constBegin(); while (it != variables.constEnd()) { ba += "["; ba += it.key().toLocal8Bit(); ba += "] = "; ba += toString(it.value()); ++it; if (it != variables.constEnd()) { ba += ", "; } } ba += ")"; return qstrdup(ba.data()); } } QHash setDefaultMKSpec(QMakeProjectFile& file) { static const QHash qmvars = QMakeConfig::queryQMake(QMakeConfig::qmakeExecutable(nullptr)); static const QString specFile = QMakeConfig::findBasicMkSpec(qmvars); if (!QFile::exists(specFile)) { qDebug() << "mkspec file does not exist:" << specFile; return {}; } QMakeMkSpecs* mkspecs = new QMakeMkSpecs(specFile, qmvars); mkspecs->read(); file.setMkSpecs(mkspecs); return qmvars; } void TestQMakeFile::varResolution() { QFETCH(QString, fileContents); QFETCH(QMakeFile::VariableMap, variables); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << fileContents; stream << flush; tmpfile.close(); QMakeFile file(tmpfile.fileName()); QVERIFY(file.read()); QCOMPARE(file.variableMap(), variables); } void TestQMakeFile::varResolution_data() { QTest::addColumn("fileContents"); QTest::addColumn("variables"); { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("1"); QTest::newRow("simple") << "VAR1 = 1\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("1"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("1"); QTest::newRow("var-in-var") << "VAR1 = 1\nVAR2 = $$VAR1\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("foo"); QTest::newRow("curlyvar") << "VAR1 = foo\nVAR2 = $${VAR1}\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QProcessEnvironment::systemEnvironment().value(QStringLiteral("USER")); QTest::newRow("qmakeshell") << "VAR1 = $$(USER)\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("foo/bar"); QTest::newRow("path") << "VAR1 = foo\nVAR2 = $$VAR1/bar\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR_1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR_2")] = QStringList() << QStringLiteral("foo/bar"); QTest::newRow("var-underscore") << "VAR_1 = foo\nVAR_2 = $$VAR_1/bar" << variables; } } void TestQMakeFile::referenceParser() { QFETCH(QString, var); VariableReferenceParser parser; parser.setContent(var); QVERIFY(parser.parse()); } void TestQMakeFile::referenceParser_data() { QTest::addColumn("var"); QTest::newRow("dot") << "."; QTest::newRow("dotdot") << ".."; } void TestQMakeFile::libTarget() { QFETCH(QString, target); QFETCH(QString, resolved); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << "TARGET = " << target << "\nTEMPLATE = lib\n"; stream << flush; tmpfile.close(); QMakeProjectFile file(tmpfile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); QCOMPARE(file.targets(), QStringList() << resolved); } void TestQMakeFile::libTarget_data() { QTest::addColumn("target"); QTest::addColumn("resolved"); QTest::newRow("simple") << "MyLib" << "MyLib"; QTest::newRow("qtLibraryTarget") << "$$qtLibraryTarget(MyLib)" << "MyLib"; QTest::newRow("qtLibraryTarget-Var") << "MyLib\nTARGET = $$qtLibraryTarget($$TARGET)" << "MyLib"; } void TestQMakeFile::defines() { QFETCH(QString, fileContents); QFETCH(DefineHash, expectedDefines); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << fileContents; stream << flush; tmpfile.close(); QMakeProjectFile file(tmpfile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); const QList list = file.defines(); QCOMPARE(list.size(), expectedDefines.size()); for (const QMakeProjectFile::DefinePair& define : list) { QVERIFY(expectedDefines.find(define.first) != expectedDefines.end()); QCOMPARE(define.second, expectedDefines[define.first]); } } void TestQMakeFile::defines_data() { QTest::addColumn("fileContents"); QTest::addColumn("expectedDefines"); { DefineHash list; list.insert(QStringLiteral("VAR1"), QString()); QTest::newRow("Simple define") << "DEFINES += VAR1" << list; } { DefineHash list; list.insert(QStringLiteral("ANSWER"), QStringLiteral("42")); QTest::newRow("Define with value") << "DEFINES += ANSWER=42" << list; } { DefineHash list; list.insert(QStringLiteral("ANSWER"), QStringLiteral("42")); list.insert(QStringLiteral("ANOTHER_DEFINE"), QString()); QTest::newRow("Multiple defines") << "DEFINES += ANSWER=42 ANOTHER_DEFINE" << list; } } void TestQMakeFile::replaceFunctions_data() { QTest::addColumn("fileContents"); QTest::addColumn("definedVariables"); QTest::addColumn("undefinedVariables"); { QString contents = "defineReplace(test) {\n" " FOO = $$1\n" " return($$FOO)\n" "}\n" "BAR = $$test(asdf)\n"; QMakeFile::VariableMap vars; vars[QStringLiteral("BAR")] = QStringList() << QStringLiteral("asdf"); QStringList undefined; undefined << QStringLiteral("FOO") << QStringLiteral("1"); QTest::newRow("defineReplace-1") << contents << vars << undefined; } } void TestQMakeFile::replaceFunctions() { QFETCH(QString, fileContents); QFETCH(QMakeFile::VariableMap, definedVariables); QFETCH(QStringList, undefinedVariables); QTemporaryFile tmpFile; tmpFile.open(); tmpFile.write(fileContents.toUtf8()); tmpFile.close(); QMakeProjectFile file(tmpFile.fileName()); setDefaultMKSpec(file); QVERIFY(file.read()); QMakeFile::VariableMap::const_iterator it = definedVariables.constBegin(); while (it != definedVariables.constEnd()) { QCOMPARE(file.variableValues(it.key()), it.value()); ++it; } foreach (const QString& var, undefinedVariables) { QVERIFY(!file.containsVariable(var)); } } void TestQMakeFile::qtIncludeDirs_data() { QTest::addColumn("fileContents"); QTest::addColumn("modules"); QTest::addColumn("missingModules"); { QStringList list; list << QStringLiteral("core") << QStringLiteral("gui"); QTest::newRow("defaults") << "" << list; } { QStringList list; list << QStringLiteral("core"); QTest::newRow("minimal") << "QT -= gui" << list; } { QStringList modules; modules << QStringLiteral("core") << QStringLiteral("gui") << QStringLiteral("network") << QStringLiteral("opengl") << QStringLiteral("phonon") << QStringLiteral("script") << QStringLiteral("scripttools") << QStringLiteral("sql") << QStringLiteral("svg") << QStringLiteral("webkit") << QStringLiteral("xml") << QStringLiteral("xmlpatterns") << QStringLiteral("qt3support") << QStringLiteral("designer") << QStringLiteral("uitools") << QStringLiteral("help") << QStringLiteral("assistant") << QStringLiteral("qtestlib") << QStringLiteral("testlib") << QStringLiteral("qaxcontainer") << QStringLiteral("qaxserver") << QStringLiteral("dbus") << QStringLiteral("declarative"); foreach (const QString& module, modules) { QStringList expected; expected << module; if (module != QLatin1String("core")) { expected << QStringLiteral("core"); } QTest::newRow(qPrintable(module)) << QStringLiteral("QT = %1").arg(module) << expected; } } } void TestQMakeFile::qtIncludeDirs() { QFETCH(QString, fileContents); QFETCH(QStringList, modules); QMap moduleMap; moduleMap[QStringLiteral("core")] = QStringLiteral("QtCore"); moduleMap[QStringLiteral("gui")] = QStringLiteral("QtGui"); moduleMap[QStringLiteral("network")] = QStringLiteral("QtNetwork"); moduleMap[QStringLiteral("opengl")] = QStringLiteral("QtOpenGL"); moduleMap[QStringLiteral("phonon")] = QStringLiteral("Phonon"); moduleMap[QStringLiteral("script")] = QStringLiteral("QtScript"); moduleMap[QStringLiteral("scripttools")] = QStringLiteral("QtScriptTools"); moduleMap[QStringLiteral("sql")] = QStringLiteral("QtSql"); moduleMap[QStringLiteral("svg")] = QStringLiteral("QtSvg"); moduleMap[QStringLiteral("webkit")] = QStringLiteral("QtWebKit"); moduleMap[QStringLiteral("xml")] = QStringLiteral("QtXml"); moduleMap[QStringLiteral("xmlpatterns")] = QStringLiteral("QtXmlPatterns"); moduleMap[QStringLiteral("qt3support")] = QStringLiteral("Qt3Support"); moduleMap[QStringLiteral("designer")] = QStringLiteral("QtDesigner"); moduleMap[QStringLiteral("uitools")] = QStringLiteral("QtUiTools"); moduleMap[QStringLiteral("help")] = QStringLiteral("QtHelp"); moduleMap[QStringLiteral("assistant")] = QStringLiteral("QtAssistant"); moduleMap[QStringLiteral("qtestlib")] = QStringLiteral("QtTest"); moduleMap[QStringLiteral("testlib")] = QStringLiteral("QtTest"); moduleMap[QStringLiteral("qaxcontainer")] = QStringLiteral("ActiveQt"); moduleMap[QStringLiteral("qaxserver")] = QStringLiteral("ActiveQt"); moduleMap[QStringLiteral("dbus")] = QStringLiteral("QtDBus"); moduleMap[QStringLiteral("declarative")] = QStringLiteral("QtDeclarative"); QTemporaryFile tmpFile; tmpFile.open(); tmpFile.write(fileContents.toUtf8()); tmpFile.close(); QMakeProjectFile file(tmpFile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); const QStringList includes = file.includeDirectories(); // should always be there QVERIFY(includes.contains(qmvars["QT_INSTALL_HEADERS"])); for (QMap::const_iterator it = moduleMap.constBegin(); it != moduleMap.constEnd(); ++it) { QFileInfo include(qmvars[QStringLiteral("QT_INSTALL_HEADERS")] + "/" + it.value()); bool shouldBeIncluded = include.exists(); if (shouldBeIncluded) { shouldBeIncluded = modules.contains(it.key()); if (!shouldBeIncluded) { foreach (const QString& module, modules) { if (module != it.key() && moduleMap.value(module) == it.value()) { shouldBeIncluded = true; break; } } } } QCOMPARE((bool)includes.contains(include.filePath()), shouldBeIncluded); } } void TestQMakeFile::testInclude() { QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QTemporaryFile includeFile(tempDir.path() + "/qmake-include"); QVERIFY(includeFile.open()); includeFile.write("DEFINES += SOME_INCLUDE_DEF\n" "SOURCES += includedFile.cpp\n" "INCLUDEPATH += $$PWD\n" "QT += webkit\n"); includeFile.close(); QTemporaryFile baseFile; baseFile.open(); baseFile.write("TEMPLATE = app\n" "TARGET = includeTest\n" "QT += network\n" "DEFINES += SOME_DEF\n" "SOURCES += file.cpp\n" /* "CONFIG += console" "# Comment to enable Debug Messages" "DEFINES += QT_NO_DEBUG_OUTPUT" "DESTDIR = ../bin" "RESOURCES = phantomjs.qrc" "HEADERS += csconverter.h \\" " phantom.h \\" " webpage.h \\" " consts.h \\" " utils.h \\" " networkaccessmanager.h \\" " cookiejar.h \\" " filesystem.h \\" " terminal.h \\" " encoding.h \\" " config.h \\" " mimesniffer.cpp \\" " third_party/mongoose/mongoose.h \\" " webserver.h" "SOURCES += phantom.cpp \\" " webpage.cpp \\" " main.cpp \\" " csconverter.cpp \\" " utils.cpp \\" " networkaccessmanager.cpp \\" " cookiejar.cpp \\" " filesystem.cpp \\" " terminal.cpp \\" " encoding.cpp \\" " config.cpp \\" " mimesniffer.cpp \\" " third_party/mongoose/mongoose.c \\" " webserver.cpp" "" "OTHER_FILES += usage.txt \\" " bootstrap.js \\" " configurator.js \\" " modules/fs.js \\" " modules/webpage.js \\" " modules/webserver.js" "" */ "include(" + includeFile.fileName().toLocal8Bit() + ")\n"); baseFile.close(); QMakeProjectFile file(baseFile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); QCOMPARE(file.variableValues("DEFINES"), QStringList() << "SOME_DEF" << "SOME_INCLUDE_DEF"); QCOMPARE(file.variableValues("SOURCES"), QStringList() << "file.cpp" << "includedFile.cpp"); QCOMPARE(file.variableValues("QT"), QStringList() << "core" << "gui" << "network" << "webkit"); // verify that include path was properly propagated QVERIFY(file.includeDirectories().contains(tempDir.path())); } void TestQMakeFile::globbing_data() { QTest::addColumn("files"); QTest::addColumn("pattern"); QTest::addColumn("matches"); QTest::newRow("wildcard-simple") << (QStringList() << QStringLiteral("foo.cpp")) << "*.cpp" << (QStringList() << QStringLiteral("foo.cpp")); QTest::newRow("wildcard-extended") << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")) << "*.cpp" << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("asdf.cpp")); QTest::newRow("wildcard-multiple") << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")) << "*.cpp *.h" << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")); QTest::newRow("wildcard-subdir") << (QStringList() << QStringLiteral("foo/bar.cpp") << QStringLiteral("fooasdf/bar.cpp") << QStringLiteral("asdf/asdf.cpp")) << "foo*/*.cpp" << (QStringList() << QStringLiteral("foo/bar.cpp") << QStringLiteral("fooasdf/bar.cpp")); QTest::newRow("bracket") << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("foo2.cpp") << QStringLiteral("fooX.cpp")) << "foo[0-9].cpp" << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("foo2.cpp")); QTest::newRow("questionmark") << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("fooX.cpp") << QStringLiteral("foo.cpp") << QStringLiteral("fooXY.cpp")) << "foo?.cpp" << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("fooX.cpp")); QTest::newRow("mixed") << (QStringList() << QStringLiteral("foo/asdf/test.cpp") << QStringLiteral("fooX/asdf1/test.cpp")) << "foo?/asdf[0-9]/*.cpp" << (QStringList() << QStringLiteral("fooX/asdf1/test.cpp")); } void TestQMakeFile::globbing() { QFETCH(QStringList, files); QFETCH(QString, pattern); QFETCH(QStringList, matches); QTemporaryDir tempDir; QDir tempDirDir(tempDir.path()); QVERIFY(tempDir.isValid()); foreach (const QString& file, files) { QVERIFY(tempDirDir.mkpath(QFileInfo(file).path())); QFile f(tempDir.path() + '/' + file); QVERIFY(f.open(QIODevice::WriteOnly)); } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write(("SOURCES = " + pattern + "\n").toUtf8()); testFile.close(); QMakeProjectFile pro(testFile.fileName()); const auto qmvars = setDefaultMKSpec(pro); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(pro.read()); QStringList actual; foreach (QString path, pro.files()) { actual << path.remove(tempDir.path() + '/'); } std::sort(actual.begin(), actual.end()); std::sort(matches.begin(), matches.end()); QCOMPARE(actual, matches); } void TestQMakeFile::benchGlobbing() { QTemporaryDir tempDir; QDir dir(tempDir.path()); const int folders = 10; const int files = 100; for (int i = 0; i < folders; ++i) { QString folder = QStringLiteral("folder%1").arg(i); dir.mkdir(folder); for (int j = 0; j < files; ++j) { QFile f1(dir.filePath(folder + QStringLiteral("/file%1.cpp").arg(j))); QVERIFY(f1.open(QIODevice::WriteOnly)); QFile f2(dir.filePath(folder + QStringLiteral("/file%1.h").arg(j))); QVERIFY(f2.open(QIODevice::WriteOnly)); } } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write("SOURCES = fo?der[0-9]/*.cpp\n"); testFile.close(); QMakeProjectFile pro(testFile.fileName()); const auto qmvars = setDefaultMKSpec(pro); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(pro.read()); int found = 0; QBENCHMARK { found = pro.files().size(); } QCOMPARE(found, files * folders); } void TestQMakeFile::benchGlobbingNoPattern() { QTemporaryDir tempDir; QDir dir(tempDir.path()); const int folders = 10; const int files = 100; for (int i = 0; i < folders; ++i) { QString folder = QStringLiteral("folder%1").arg(i); dir.mkdir(folder); for (int j = 0; j < files; ++j) { QFile f1(dir.filePath(folder + QStringLiteral("/file%1.cpp").arg(j))); QVERIFY(f1.open(QIODevice::WriteOnly)); QFile f2(dir.filePath(folder + QStringLiteral("/file%1.h").arg(j))); QVERIFY(f2.open(QIODevice::WriteOnly)); } } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write("SOURCES = folder0/file1.cpp\n"); testFile.close(); QMakeProjectFile pro(testFile.fileName()); const auto qmvars = setDefaultMKSpec(pro); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(pro.read()); int found = 0; QBENCHMARK { found = pro.files().size(); } QCOMPARE(found, 1); } diff --git a/plugins/qmljs/codecompletion/context.cpp b/plugins/qmljs/codecompletion/context.cpp index e6635fa7c0..d522df407e 100644 --- a/plugins/qmljs/codecompletion/context.cpp +++ b/plugins/qmljs/codecompletion/context.cpp @@ -1,508 +1,508 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2013 Sven Brauch * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "context.h" #include "items/modulecompletionitem.h" #include "items/functioncalltipcompletionitem.h" #include #include #include #include #include #include #include #include #include #include "../duchain/expressionvisitor.h" #include "../duchain/helper.h" #include "../duchain/cache.h" #include "../duchain/frameworks/nodejs.h" #include #include using namespace KDevelop; -typedef QPair DeclarationDepthPair; +using DeclarationDepthPair = QPair; namespace QmlJS { CodeCompletionContext::CodeCompletionContext(const DUContextPointer& context, const QString& text, const CursorInRevision& position, int depth) : KDevelop::CodeCompletionContext(context, extractLastLine(text), position, depth), m_completionKind(NormalCompletion) { // Detect "import ..." and provide import completions if (m_text.startsWith(QLatin1String("import "))) { m_completionKind = ImportCompletion; } // Node.js module completions if (m_text.endsWith(QLatin1String("require("))) { m_completionKind = NodeModulesCompletion; } // Detect whether the cursor is in a comment bool isLastLine = true; bool inString = false; for (int index = text.size()-1; index > 0; --index) { const QChar c = text.at(index); const QChar prev = text.at(index - 1); if (c == QLatin1Char('\n')) { isLastLine = false; } else if (isLastLine && prev == QLatin1Char('/') && c == QLatin1Char('/')) { // Single-line comment on the current line, we are in a comment m_completionKind = CommentCompletion; break; } else if (prev == QLatin1Char('/') && c == QLatin1Char('*')) { // Start of a multi-line comment encountered m_completionKind = CommentCompletion; break; } else if (prev == QLatin1Char('*') && c == QLatin1Char('/')) { // End of a multi-line comment. Because /* and */ cannot be nested, // encountering a */ is enough to know that the cursor is outside a // comment break; } else if (prev != QLatin1Char('\\') && (c == QLatin1Char('"') || c == QLatin1Char('\''))) { // Toggle whether we are in a string or not inString = !inString; } } if (inString) { m_completionKind = StringCompletion; } // Some specific constructs don't need any code-completion at all (mainly // because the user will declare new things, not use ones) if (m_text.contains(QRegExp(QLatin1String("(var|function)\\s+$"))) || // "var |" or "function |" m_text.contains(QRegExp(QLatin1String("property\\s+[a-zA-Z0-9_]+\\s+$"))) || // "property |" m_text.contains(QRegExp(QLatin1String("function(\\s+[a-zA-Z0-9_]+)?\\s*\\($"))) || // "function (|" or "function (|" m_text.contains(QRegExp(QLatin1String("id:\\s*"))) // "id: |" ) { m_completionKind = NoCompletion; } } QList CodeCompletionContext::completionItems(bool& abort, bool fullCompletion) { Q_UNUSED (fullCompletion); if (abort) { return QList(); } switch (m_completionKind) { case NormalCompletion: return normalCompletion(); case CommentCompletion: return commentCompletion(); case ImportCompletion: return importCompletion(); case NodeModulesCompletion: return nodeModuleCompletions(); case StringCompletion: case NoCompletion: break; } return QList(); } AbstractType::Ptr CodeCompletionContext::typeToMatch() const { return m_typeToMatch; } QList CodeCompletionContext::normalCompletion() { QList items; QChar lastChar = m_text.size() > 0 ? m_text.at(m_text.size() - 1) : QLatin1Char('\0'); bool inQmlObjectScope = (m_duContext->type() == DUContext::Class); // Start with the function call-tips, because functionCallTips is also responsible // for setting m_declarationForTypeMatch items << functionCallTips(); if (lastChar == QLatin1Char('.') || lastChar == QLatin1Char('[')) { // Offer completions for object members and array subscripts items << fieldCompletions( m_text.left(m_text.size() - 1), lastChar == QLatin1Char('[') ? CompletionItem::QuotesAndBracket : CompletionItem::NoDecoration ); } // "object." must only display the members of object, the declarations // available in the current context. if (lastChar != QLatin1Char('.')) { if (inQmlObjectScope) { DUChainReadLocker lock; // The cursor is in a QML object and there is nothing before it. Display // a list of properties and signals that can be used in a script binding. // Note that the properties/signals of parent QML objects are not displayed here items << completionsInContext(m_duContext, CompletionOnlyLocal | CompletionHideWrappers, CompletionItem::ColonOrBracket); items << completionsFromImports(CompletionHideWrappers); items << completionsInContext(DUContextPointer(m_duContext->topContext()), CompletionHideWrappers, CompletionItem::NoDecoration); } else { items << completionsInContext(m_duContext, CompletionInContextFlags(), CompletionItem::NoDecoration); items << completionsFromImports(CompletionInContextFlags()); items << completionsFromNodeModule(CompletionInContextFlags(), QStringLiteral("__builtin_ecmascript")); if (!QmlJS::isQmlFile(m_duContext.data())) { items << completionsFromNodeModule(CompletionInContextFlags(), QStringLiteral("__builtin_dom")); } } } return items; } QList CodeCompletionContext::commentCompletion() { return QList(); } QList CodeCompletionContext::importCompletion() { QList items; QString fragment = m_text.section(QLatin1Char(' '), -1, -1); // Use the cache to find the directory corresponding to the fragment // (org.kde is, for instance, /usr/lib64/kde4/imports/org/kde), and list // its subdirectories QString dataDir = Cache::instance().modulePath(m_duContext->url(), fragment); QDir dir; if (!dataDir.isEmpty()) { dir.setPath(dataDir); const auto dirEntries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); items.reserve(dirEntries.size()); for (const QString& entry : dirEntries) { items.append(CompletionTreeItemPointer(new ModuleCompletionItem( fragment + entry.section(QLatin1Char('.'), 0, 0), ModuleCompletionItem::Import ))); } } return items; } QList CodeCompletionContext::nodeModuleCompletions() { QList items; QDir dir; const auto& paths = NodeJS::instance().moduleDirectories(m_duContext->url().str()); for (auto& path : paths) { dir.setPath(path.toLocalFile()); const auto& entries = dir.entryList(QDir::Files, QDir::Name); for (QString entry : entries) { entry.remove(QLatin1String(".js")); if (entry.startsWith(QLatin1String("__"))) { // Internal module, don't show continue; } items.append(CompletionTreeItemPointer( new ModuleCompletionItem(entry, ModuleCompletionItem::Quotes) )); } } return items; } QList CodeCompletionContext::functionCallTips() { Stack stack = expressionStack(m_text); QList items; int argumentHintDepth = 1; bool isTopOfStack = true; DUChainReadLocker lock; while (!stack.isEmpty()) { ExpressionStackEntry entry = stack.pop(); if (isTopOfStack && entry.operatorStart > entry.startPosition) { // Deduce the declaration for type matching using operatorStart: // // table[document.base + // [ ^ // // ^ = operatorStart. Just before operatorStart is a code snippet that ends // with the declaration whose type should be used. DeclarationPointer decl = declarationAtEndOfString(m_text.left(entry.operatorStart)); if (decl) { m_typeToMatch = decl->abstractType(); } } if (entry.startPosition > 0 && m_text.at(entry.startPosition - 1) == QLatin1Char('(')) { // The current entry represents a function call, create a call-tip for it DeclarationPointer functionDecl = declarationAtEndOfString(m_text.left(entry.startPosition - 1)); if (functionDecl) { auto item = new FunctionCalltipCompletionItem( functionDecl, argumentHintDepth, entry.commas ); items << CompletionTreeItemPointer(item); argumentHintDepth++; if (isTopOfStack && !m_typeToMatch) { m_typeToMatch = item->currentArgumentType(); } } } isTopOfStack = false; } return items; } QList CodeCompletionContext::completionsFromImports(CompletionInContextFlags flags) { QList items; // Iterate over all the imported namespaces and add their definitions DUChainReadLocker lock; const QList imports = m_duContext->findDeclarations(globalImportIdentifier()); QList realImports; for (Declaration* import : imports) { if (import->kind() != Declaration::NamespaceAlias) { continue; } auto* decl = static_cast(import); realImports << m_duContext->findDeclarations(decl->importIdentifier()); } items.reserve(realImports.size()); foreach (Declaration* import, realImports) { items << completionsInContext( DUContextPointer(import->internalContext()), flags, CompletionItem::NoDecoration ); } return items; } QList CodeCompletionContext::completionsFromNodeModule(CompletionInContextFlags flags, const QString& module) { return completionsInContext( DUContextPointer(QmlJS::getInternalContext( QmlJS::NodeJS::instance().moduleExports(module, m_duContext->url()) )), flags | CompletionOnlyLocal, CompletionItem::NoDecoration ); } QList CodeCompletionContext::completionsInContext(const DUContextPointer& context, CompletionInContextFlags flags, CompletionItem::Decoration decoration) { QList items; DUChainReadLocker lock; if (context) { const auto declarations = context->allDeclarations( CursorInRevision::invalid(), context->topContext(), !flags.testFlag(CompletionOnlyLocal) ); for (const DeclarationDepthPair& decl : declarations) { DeclarationPointer declaration(decl.first); CompletionItem::Decoration decorationOfThisItem = decoration; if (declaration->identifier() == globalImportIdentifier()) { continue; } if (declaration->qualifiedIdentifier().isEmpty()) { continue; } else if (context->owner() && ( context->owner()->kind() == Declaration::Namespace || context->owner()->kind() == Declaration::NamespaceAlias ) && decl.second != 0 && decl.second != 1001) { // Only show the local declarations of modules, or the declarations // immediately in its imported parent contexts (that are global // contexts, hence the distance of 1001). This prevens "String()", // "QtQuick1.0" and "builtins" from being listed when the user // types "PlasmaCore.". continue; } else if (decorationOfThisItem == CompletionItem::NoDecoration && declaration->abstractType() && declaration->abstractType()->whichType() == AbstractType::TypeFunction) { // Decorate function calls with brackets decorationOfThisItem = CompletionItem::Brackets; } else if (flags.testFlag(CompletionHideWrappers)) { auto* classDecl = dynamic_cast(declaration.data()); if (classDecl && classDecl->classType() == ClassDeclarationData::Interface) { continue; } } items << CompletionTreeItemPointer(new CompletionItem(declaration, decl.second, decorationOfThisItem)); } } return items; } QList CodeCompletionContext::fieldCompletions(const QString& expression, CompletionItem::Decoration decoration) { // The statement given to this method ends with an expression that may identify // a declaration ("foo" in "test(1, 2, foo"). List the declarations of this // inner context DUContext* context = getInternalContext(declarationAtEndOfString(expression)); if (context) { return completionsInContext(DUContextPointer(context), CompletionOnlyLocal, decoration); } else { return QList(); } } Stack CodeCompletionContext::expressionStack(const QString& expression) { Stack stack; ExpressionStackEntry entry; QmlJS::Lexer lexer(nullptr); bool atEnd = false; lexer.setCode(expression, 1, false); entry.startPosition = 0; entry.operatorStart = 0; entry.operatorEnd = 0; entry.commas = 0; stack.push(entry); // NOTE: KDevelop uses 0-indexed columns while QMLJS uses 1-indexed columns while (!atEnd) { switch (lexer.lex()) { case QmlJSGrammar::EOF_SYMBOL: atEnd = true; break; case QmlJSGrammar::T_LBRACE: case QmlJSGrammar::T_LBRACKET: case QmlJSGrammar::T_LPAREN: entry.startPosition = lexer.tokenEndColumn() - 1; entry.operatorStart = entry.startPosition; entry.operatorEnd = entry.startPosition; entry.commas = 0; stack.push(entry); break; case QmlJSGrammar::T_RBRACE: case QmlJSGrammar::T_RBRACKET: case QmlJSGrammar::T_RPAREN: if (stack.count() > 1) { stack.pop(); } break; case QmlJSGrammar::T_IDENTIFIER: case QmlJSGrammar::T_DOT: case QmlJSGrammar::T_THIS: break; case QmlJSGrammar::T_COMMA: stack.top().commas++; break; default: // The last operator of every sub-expression is stored on the stack // so that "A = foo." can know that attributes of foo having the same // type as A should be highlighted. stack.top().operatorStart = lexer.tokenStartColumn() - 1; stack.top().operatorEnd = lexer.tokenEndColumn() - 1; break; } } return stack; } DeclarationPointer CodeCompletionContext::declarationAtEndOfString(const QString& expression) { // Build the expression stack of expression and use the valid portion of the // top sub-expression to find the right-most declaration that can be found // in expression. QmlJS::Document::MutablePtr doc = QmlJS::Document::create(QStringLiteral("inline"), Dialect::JavaScript); ExpressionStackEntry topEntry = expressionStack(expression).top(); doc->setSource(expression.mid(topEntry.operatorEnd)); doc->parseExpression(); if (!doc || !doc->isParsedCorrectly()) { return DeclarationPointer(); } // Use ExpressionVisitor to find the type (and associated declaration) of // the snippet that has been parsed. The inner context of the declaration // can be used to get the list of completions ExpressionVisitor visitor(m_duContext.data()); doc->ast()->accept(&visitor); return visitor.lastDeclaration(); } bool CodeCompletionContext::containsOnlySpaces(const QString& str) { for (int i=0; i * * Copyright (C) 2012 by Milian Wolff * * * * 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 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 CONTEXTBUILDER_H #define CONTEXTBUILDER_H #include #include #include #include #include #include "duchainexport.h" class ParseSession; -typedef KDevelop::AbstractContextBuilder ContextBuilderBase; +using ContextBuilderBase = KDevelop::AbstractContextBuilder; class KDEVQMLJSDUCHAIN_EXPORT ContextBuilder : public ContextBuilderBase, public QmlJS::AST::Visitor { public: struct ExpressionType { KDevelop::AbstractType::Ptr type; KDevelop::DeclarationPointer declaration; bool isPrototype; }; public: ContextBuilder(); void startVisiting(QmlJS::AST::Node* node) override; KDevelop::RangeInRevision editorFindRange(QmlJS::AST::Node* fromNode, QmlJS::AST::Node* toNode) override; KDevelop::QualifiedIdentifier identifierForNode(QmlJS::AST::IdentifierPropertyName* node) override; void setContextOnNode(QmlJS::AST::Node* node, KDevelop::DUContext* context) override; KDevelop::DUContext* contextFromNode(QmlJS::AST::Node* node) override; KDevelop::TopDUContext* newTopContext(const KDevelop::RangeInRevision& range, KDevelop::ParsingEnvironmentFile* file = nullptr) override; KDevelop::DUContext* newContext(const KDevelop::RangeInRevision& range) override; ExpressionType findType(QmlJS::AST::Node* node); void setParseSession(ParseSession* session); protected: ParseSession* m_session; }; #endif // CONTEXTBUILDER_H diff --git a/plugins/qmljs/duchain/declarationbuilder.h b/plugins/qmljs/duchain/declarationbuilder.h index f1d9b70330..b7cbb365e8 100644 --- a/plugins/qmljs/duchain/declarationbuilder.h +++ b/plugins/qmljs/duchain/declarationbuilder.h @@ -1,177 +1,177 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * * * 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 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 DECLARATIONBUILDER_H #define DECLARATIONBUILDER_H #include #include #include "contextbuilder.h" #include namespace KDevelop { class ClassDeclaration; } namespace QmlJS { class NodeJS; } -typedef KDevelop::AbstractTypeBuilder TypeBuilder; -typedef KDevelop::AbstractDeclarationBuilder DeclarationBuilderBase; +using TypeBuilder = KDevelop::AbstractTypeBuilder; +using DeclarationBuilderBase = KDevelop::AbstractDeclarationBuilder; class KDEVQMLJSDUCHAIN_EXPORT DeclarationBuilder : public DeclarationBuilderBase { friend class QmlJS::NodeJS; public: explicit DeclarationBuilder(ParseSession* session); KDevelop::ReferencedTopDUContext build(const KDevelop::IndexedString& url, QmlJS::AST::Node* node, const KDevelop::ReferencedTopDUContext& updateContext = KDevelop::ReferencedTopDUContext()) override; void startVisiting(QmlJS::AST::Node* node) override; protected: using Visitor::visit; using Visitor::endVisit; - typedef QList> ExportLiteralsAndNames; + using ExportLiteralsAndNames = QList>; // Functions template void declareFunction(QmlJS::AST::Node* node, bool newPrototypeContext, const KDevelop::Identifier& name, const KDevelop::RangeInRevision& nameRange, QmlJS::AST::Node* parameters, const KDevelop::RangeInRevision& parametersRange, QmlJS::AST::Node* body, const KDevelop::RangeInRevision& bodyRange); template void declareParameters(Node* node, QmlJS::AST::UiQualifiedId* Node::*typeFunc); void endVisitFunction(); // Set the return type of the function to void if no return statement has been encountered bool visit(QmlJS::AST::FunctionDeclaration* node) override; bool visit(QmlJS::AST::FunctionExpression* node) override; bool visit(QmlJS::AST::FormalParameterList* node) override; bool visit(QmlJS::AST::UiParameterList* node) override; bool visit(QmlJS::AST::ReturnStatement* node) override; void endVisit(QmlJS::AST::FunctionDeclaration* node) override; void endVisit(QmlJS::AST::FunctionExpression* node) override; // Variables /// NOTE: this visits the @p base node and its children void inferArgumentsFromCall(QmlJS::AST::Node* base, QmlJS::AST::ArgumentList* arguments); bool visit(QmlJS::AST::VariableDeclaration* node) override; void endVisit(QmlJS::AST::VariableDeclaration* node) override; bool visit(QmlJS::AST::BinaryExpression* node) override; bool visit(QmlJS::AST::CallExpression* node) override; bool visit(QmlJS::AST::NewMemberExpression* node) override; // Arrays void declareFieldMember(const KDevelop::DeclarationPointer& declaration, const QString& member, QmlJS::AST::Node* node, const QmlJS::AST::SourceLocation& location); bool visit(QmlJS::AST::FieldMemberExpression* node) override; bool visit(QmlJS::AST::ArrayMemberExpression* node) override; bool visit(QmlJS::AST::ObjectLiteral* node) override; bool visit(QmlJS::AST::PropertyNameAndValue* node) override; void endVisit(QmlJS::AST::PropertyNameAndValue* node) override; void endVisit(QmlJS::AST::ObjectLiteral* node) override; // plugin.qmltypes void declareComponent(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision &range, const KDevelop::Identifier &name); void declareMethod(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision &range, const KDevelop::Identifier &name, bool isSlot, bool isSignal); void declareProperty(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision &range, const KDevelop::Identifier &name); void declareParameter(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision &range, const KDevelop::Identifier &name); void declareEnum(const KDevelop::RangeInRevision &range, const KDevelop::Identifier &name); void declareComponentSubclass(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision& range, const QString& baseclass, QmlJS::AST::UiQualifiedId* qualifiedId); void declareComponentInstance(QmlJS::AST::ExpressionStatement *expression); ExportLiteralsAndNames exportedNames(QmlJS::AST::ExpressionStatement *exports); void declareExports(const ExportLiteralsAndNames& exports, KDevelop::ClassDeclaration* classdecl); // UI void importDirectory(const QString& directory, QmlJS::AST::UiImport* node); void importModule(QmlJS::AST::UiImport* node); bool visit(QmlJS::AST::UiImport* node) override; bool visit(QmlJS::AST::UiObjectDefinition* node) override; void endVisit(QmlJS::AST::UiObjectDefinition* node) override; bool visit(QmlJS::AST::UiScriptBinding* node) override; void endVisit(QmlJS::AST::UiScriptBinding* node) override; bool visit(QmlJS::AST::UiObjectBinding* node) override; void endVisit(QmlJS::AST::UiObjectBinding* node) override; bool visit(QmlJS::AST::UiPublicMember* node) override; void endVisit(QmlJS::AST::UiPublicMember* node) override; protected: template DeclarationT* openDeclaration(const KDevelop::Identifier& id, const KDevelop::RangeInRevision& newRange, DeclarationFlags flags = NoFlags) { auto* res = DeclarationBuilderBase::openDeclaration(id, newRange, flags); res->setAlwaysForceDirect(true); return res; } private: void closeAndAssignType(); void registerBaseClasses(); /*!< @brief Enumerates the base classes of the current class and import their inner contexts */ void addBaseClass(KDevelop::ClassDeclaration* classDecl, const QString &name); /*!< @brief Add a base class to a class declaration */ void addBaseClass(KDevelop::ClassDeclaration* classDecl, const KDevelop::IndexedType& type); KDevelop::AbstractType::Ptr typeFromName(const QString& name); /*!< @brief Type from a general name (int, string, or a class name) */ KDevelop::AbstractType::Ptr typeFromClassName(const QString& name); /*!< @brief Type from a class name, built-in types are not supported here */ bool areTypesEqual(const KDevelop::AbstractType::Ptr& a, const KDevelop::AbstractType::Ptr& b); using DeclarationBuilderBase::setComment; void setComment(QmlJS::AST::Node* node); private: bool m_prebuilding; KDevelop::Stack m_skipEndVisit; }; #endif // DECLARATIONBUILDER_H diff --git a/plugins/qmljs/duchain/frameworks/nodejs.h b/plugins/qmljs/duchain/frameworks/nodejs.h index 4039206b89..00aaaa216e 100644 --- a/plugins/qmljs/duchain/frameworks/nodejs.h +++ b/plugins/qmljs/duchain/frameworks/nodejs.h @@ -1,87 +1,87 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2014 Denis Steckelmacher * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef __FRAMEWORK_NODEJS_H__ #define __FRAMEWORK_NODEJS_H__ #include "../declarationbuilder.h" #include "duchainexport.h" #include #include #include namespace QmlJS { /** * Singleton implementing support for the Node.js Javascript framework */ class KDEVQMLJSDUCHAIN_EXPORT NodeJS { private: NodeJS(); public: static NodeJS& instance(); /** * Initialize a QML/JS context so that it contains built-in Node.js declarations * that will allow the module to export symbols. * * @note The DUChain must be write-locked */ void initialize(DeclarationBuilder* builder); /** * Declaration of the "exports" (or "module.exports") member of a Node.js * module. * * @param moduleName Name of the module to import ("http", "../module.js", etc) * @param url Url of the document that imports the module */ KDevelop::DeclarationPointer moduleExports(const QString& moduleName, const KDevelop::IndexedString& url); /** * Declaration of a member of a module */ KDevelop::DeclarationPointer moduleMember(const QString& moduleName, const QString& memberName, const KDevelop::IndexedString& url); /** * List of directories where Node.js modules visible from @p url may exist */ KDevelop::Path::List moduleDirectories(const QString& url); private: void createObject(const QString& name, int index, DeclarationBuilder* builder); QString moduleFileName(const QString& moduleName, const QString& url); QString fileOrDirectoryPath(const QString& baseName); private: - typedef QHash, QString> CachedModuleFileNamesHash; + using CachedModuleFileNamesHash = QHash, QString>; CachedModuleFileNamesHash m_cachedModuleFileNames; QMutex m_mutex; }; } #endif diff --git a/plugins/qmljs/duchain/functiondeclaration.h b/plugins/qmljs/duchain/functiondeclaration.h index 62b30dce20..c5b0a3e8b5 100644 --- a/plugins/qmljs/duchain/functiondeclaration.h +++ b/plugins/qmljs/duchain/functiondeclaration.h @@ -1,95 +1,95 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2014 Denis Steckelmacher * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef __FUNCTIONDECLARATION_H__ #define __FUNCTIONDECLARATION_H__ #include "duchainexport.h" #include #include #include namespace QmlJS { class KDEVQMLJSDUCHAIN_EXPORT FunctionDeclarationData : public KDevelop::FunctionDeclarationData { public: KDevelop::IndexedDUContext m_prototypeContext; }; /** * @brief Function declaration keeping track of a "prototype" context * * The prototype of a function can be used, in Javascript, to add methods and * members to the objects instantiated by calling the function. * * The prototype is also used to resolve "this". If a function is assigned to * an object member, its "prototype" becomes the internal context of the object. * This way, functions assigned to members of the prototype of a class can use * "this" to refer to the object on which they are called. * * @code * function Class() { this.name = "Me"; } * * Class.prototype.print = function() { console.log(this.name) } * @endcode */ class KDEVQMLJSDUCHAIN_EXPORT FunctionDeclaration : public KDevelop::FunctionDeclaration { public: FunctionDeclaration(const FunctionDeclaration &rhs); FunctionDeclaration(const KDevelop::RangeInRevision &range, KDevelop::DUContext *context); explicit FunctionDeclaration(FunctionDeclarationData &data); ~FunctionDeclaration() override; /** * @brief Return the context representing the prototype of this function * * The returned context, if not null, contains the declarations of the members * of the prototype. * * @note The DUChain must be read-locked */ KDevelop::DUContext* prototypeContext() const; /** * @brief Set the prototype context of this function * * @note The DUChain must be write-locked */ void setPrototypeContext(KDevelop::DUContext* context); enum { Identity = 112 }; - typedef KDevelop::DUChainPointer Ptr; + using Ptr = KDevelop::DUChainPointer; private: DUCHAIN_DECLARE_DATA(FunctionDeclaration) }; } DUCHAIN_DECLARE_TYPE(QmlJS::FunctionDeclaration) #endif diff --git a/plugins/qmljs/duchain/functiontype.h b/plugins/qmljs/duchain/functiontype.h index f9d1812cae..9e4e1cdec6 100644 --- a/plugins/qmljs/duchain/functiontype.h +++ b/plugins/qmljs/duchain/functiontype.h @@ -1,63 +1,63 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2014 Denis Steckelmacher * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef __FUNCTIONTYPE_H__ #define __FUNCTIONTYPE_H__ #include "duchainexport.h" #include #include #include #include namespace QmlJS { -typedef KDevelop::MergeIdentifiedType FunctionTypeBase; +using FunctionTypeBase = KDevelop::MergeIdentifiedType; /** * Function type bound to a function declaration, so that the name of its parameters * can be displayed when needed */ class KDEVQMLJSDUCHAIN_EXPORT FunctionType : public FunctionTypeBase { public: - typedef KDevelop::TypePtr Ptr; + using Ptr = KDevelop::TypePtr; FunctionType(); FunctionType(const FunctionType& rhs); explicit FunctionType(Data& data); ~FunctionType() override; KDevelop::AbstractType* clone() const override; QString toString() const override; uint hash() const override; enum { Identity = 30 }; - typedef FunctionTypeBase::Data Data; + using Data = FunctionTypeBase::Data; }; } #endif diff --git a/plugins/qmljs/duchain/parsesession.h b/plugins/qmljs/duchain/parsesession.h index 91a1242b87..5f52b50275 100644 --- a/plugins/qmljs/duchain/parsesession.h +++ b/plugins/qmljs/duchain/parsesession.h @@ -1,187 +1,187 @@ /************************************************************************************* * Copyright (C) 2012 by Milian Wolff * * * * 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 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 PARSESESSION_H #define PARSESESSION_H #include #include #include #include #include #include "duchainexport.h" namespace KDevelop { class SimpleRange; class RangeInRevision; } -typedef QPair SimpleUse; +using SimpleUse = QPair; /** * This class wraps the qmljs parser and offers some helper functions * that make it simpler to use the parser in KDevelop. */ class KDEVQMLJSDUCHAIN_EXPORT ParseSession { public: /** * @return a unique identifier for QML/JS documents. */ static KDevelop::IndexedString languageString(); /** * @return the QML/JS language corresponding to a file suffix */ static QmlJS::Dialect guessLanguageFromSuffix(const QString& path); /** * Parse the given @p contents. * * @param url The url for the document you want to parse. * @param contents The contents of the document you want to parse. */ ParseSession(const KDevelop::IndexedString& url, const QString& contents, int priority); /** * @return the URL of this session */ KDevelop::IndexedString url() const; /** * @return The module name of this file ("/foo/QtQuick.qml" yields "QtQuick") */ QString moduleName() const; /** * @return true if the document was properly parsed, false otherwise. */ bool isParsedCorrectly() const; /** * @return the root AST node or null if it failed to parse. */ QmlJS::AST::Node* ast() const; /** * Add a problem concerning the given range */ void addProblem(QmlJS::AST::Node* node, const QString& message, KDevelop::IProblem::Severity severity = KDevelop::IProblem::Warning); /** * @return the problems encountered during parsing. */ QList problems() const; /** * @return the string representation of @p location. */ QString symbolAt(const QmlJS::AST::SourceLocation& location) const; /** * @return the language of the parsed document. */ QmlJS::Dialect language() const; /** * @return the comment related to the given source location or an empty string */ QString commentForLocation(const QmlJS::AST::SourceLocation& location) const; /** * Convert @p location to a KDevelop::RangeInRevision and return that. */ KDevelop::RangeInRevision locationToRange(const QmlJS::AST::SourceLocation& location) const; /** * Convert @p locationFrom and @p locationTo to a KDevelop::RangeInRevision and return that. */ KDevelop::RangeInRevision locationsToRange(const QmlJS::AST::SourceLocation& locationFrom, const QmlJS::AST::SourceLocation& locationTo) const; /** * Range that starts at the end of the first token, and ends at the beginning of the second token */ KDevelop::RangeInRevision locationsToInnerRange(const QmlJS::AST::SourceLocation& locationFrom, const QmlJS::AST::SourceLocation& locationTo) const; /** * @return a range that spans @p fromNode and @p toNode. */ KDevelop::RangeInRevision editorFindRange(QmlJS::AST::Node* fromNode, QmlJS::AST::Node* toNode) const; void setContextOnNode(QmlJS::AST::Node* node, KDevelop::DUContext* context); KDevelop::DUContext* contextFromNode(QmlJS::AST::Node* node) const; /** * Return whether all the files included by this file were already present in * the DUChain. */ bool allDependenciesSatisfied() const; /** * Return the context of a given QML file, NULL if this file is not yet known * to the DUChain. * * When a file that exists is passed to this method and the file hasn't yet * been parsed, it is queued for parsing, and the current file will also be * re-parsed after it. */ KDevelop::ReferencedTopDUContext contextOfFile(const QString &fileName); /** * Static version of contextOfFile. The @p url parameter is used to trigger * a reparse of @p url if @p fileName was not yet in the DUChain */ static KDevelop::ReferencedTopDUContext contextOfFile(const QString& fileName, const KDevelop::IndexedString& url, int ownPriority); /** * Schedule for update all the files that depend on this file */ void reparseImporters(); /** * Schedule a document for update using the default flags of QML/JS */ static void scheduleForParsing(const KDevelop::IndexedString& url, int priority); /** * Dump AST tree to stdout. */ void dumpNode(QmlJS::AST::Node* node) const; private: KDevelop::IndexedString m_url; QString m_baseName; QmlJS::Document::MutablePtr m_doc; int m_ownPriority; bool m_allDependenciesSatisfied; QList m_problems; - typedef QHash NodeToContextHash; + using NodeToContextHash = QHash; NodeToContextHash m_astToContext; }; #endif // PARSESESSION_H diff --git a/plugins/qmljs/duchain/qmljsducontext.h b/plugins/qmljs/duchain/qmljsducontext.h index 5d7651610b..aff6061cbc 100644 --- a/plugins/qmljs/duchain/qmljsducontext.h +++ b/plugins/qmljs/duchain/qmljsducontext.h @@ -1,83 +1,83 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2013 Sven Brauch * Copyright (c) 2014 Denis Steckelmacher * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef __QMLJSDUCONTEXT_H__ #define __QMLJSDUCONTEXT_H__ #include #include class QWidget; namespace KDevelop { class Declaration; class TopDUContext; } namespace QmlJS { template class QmlJSDUContext : public BaseContext { public: template explicit QmlJSDUContext(Data& data) : BaseContext(data) { } ///Parameters will be reached to the base-class template QmlJSDUContext(const Param1& p1, const Param2& p2, bool isInstantiationContext) : BaseContext(p1, p2, isInstantiationContext) { static_cast(this)->d_func_dynamic()->setClassId(this); } ///Both parameters will be reached to the base-class. This fits TopDUContext. template QmlJSDUContext(const Param1& p1, const Param2& p2, const Param3& p3) : BaseContext(p1, p2, p3) { static_cast(this)->d_func_dynamic()->setClassId(this); } template QmlJSDUContext(const Param1& p1, const Param2& p2) : BaseContext(p1, p2) { static_cast(this)->d_func_dynamic()->setClassId(this); } QWidget* createNavigationWidget(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix, KDevelop::AbstractNavigationWidget::DisplayHints hints) const override; enum { Identity = IdentityT }; }; -typedef QmlJSDUContext QmlJSTopDUContext; -typedef QmlJSDUContext QmlJSNormalDUContext; +using QmlJSTopDUContext = QmlJSDUContext; +using QmlJSNormalDUContext = QmlJSDUContext; } DUCHAIN_DECLARE_TYPE(QmlJS::QmlJSTopDUContext) DUCHAIN_DECLARE_TYPE(QmlJS::QmlJSNormalDUContext) #endif diff --git a/plugins/qmljs/duchain/usebuilder.h b/plugins/qmljs/duchain/usebuilder.h index 09676cbae0..9b794ddf99 100644 --- a/plugins/qmljs/duchain/usebuilder.h +++ b/plugins/qmljs/duchain/usebuilder.h @@ -1,56 +1,56 @@ /************************************************************************************* * Copyright (C) 2013 by Andrea Scarpino * * * * 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 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 USEBUILDER_H #define USEBUILDER_H #include #include #include "contextbuilder.h" -typedef KDevelop::AbstractUseBuilder UseBuilderBase; +using UseBuilderBase = KDevelop::AbstractUseBuilder; class KDEVQMLJSDUCHAIN_EXPORT UseBuilder : public UseBuilderBase { public: explicit UseBuilder(ParseSession* session); protected: using Visitor::visit; using Visitor::preVisit; using Visitor::postVisit; bool preVisit(QmlJS::AST::Node* node) override; void postVisit(QmlJS::AST::Node* node) override; bool visit(QmlJS::AST::FieldMemberExpression* node) override; bool visit(QmlJS::AST::IdentifierExpression* node) override; bool visit(QmlJS::AST::UiQualifiedId* node) override; bool visit(QmlJS::AST::UiImport* node) override; bool visit(QmlJS::AST::UiPublicMember* node) override; bool visit(QmlJS::AST::UiScriptBinding* node) override; private: void useForExpression(QmlJS::AST::Node* node, const KDevelop::RangeInRevision &range = KDevelop::RangeInRevision::invalid()); private: KDevelop::Stack m_nodesThatOpenedContexts; }; #endif // USEBUILDER_H diff --git a/plugins/quickopen/duchainitemquickopen.h b/plugins/quickopen/duchainitemquickopen.h index d7a1a71cf1..e386257590 100644 --- a/plugins/quickopen/duchainitemquickopen.h +++ b/plugins/quickopen/duchainitemquickopen.h @@ -1,100 +1,100 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 DUCHAIN_ITEM_QUICKOPEN #define DUCHAIN_ITEM_QUICKOPEN #include #include #include #include namespace KDevelop { class IQuickOpen; } struct DUChainItem { DUChainItem() { } KDevelop::IndexedDeclaration m_item; QString m_text; KDevelop::Path m_projectPath; bool m_noHtmlDestription = false; }; Q_DECLARE_TYPEINFO(DUChainItem, Q_MOVABLE_TYPE); class DUChainItemData : public KDevelop::QuickOpenDataBase { public: explicit DUChainItemData(const DUChainItem& item, bool openDefinition = false); QString text() const override; QString htmlDescription() const override; QList highlighting() const override; bool execute(QString& filterText) override; bool isExpandable() const override; QWidget* expandingWidget() const override; QIcon icon() const override; KDevelop::Path projectPath() const; private: DUChainItem m_item; bool m_openDefinition; }; /** * A QuickOpenDataProvider that presents a list of declarations. * The declarations need to be set using setItems(..) in a re-implemented reset() function. * */ class DUChainItemDataProvider : public KDevelop::QuickOpenDataProviderBase , public KDevelop::Filter { Q_OBJECT public: - typedef KDevelop::Filter Base; + using Base = KDevelop::Filter; /// When openDefinitions is true, the definitions will be opened if available on execute(). explicit DUChainItemDataProvider(KDevelop::IQuickOpen* quickopen, bool openDefinitions = false); void setFilterText(const QString& text) override; uint itemCount() const override; uint unfilteredItemCount() const override; KDevelop::QuickOpenDataPointer data(uint row) const override; void reset() override; protected: //Override to create own DUChainItemData derived classes DUChainItemData* createData(const DUChainItem& item) const; //Reimplemented from Base<..> QString itemText(const DUChainItem& data) const override; KDevelop::IQuickOpen* m_quickopen; private: bool m_openDefinitions; }; #endif diff --git a/plugins/quickopen/projectitemquickopen.h b/plugins/quickopen/projectitemquickopen.h index 5975e0b2f7..f7bb9b9471 100644 --- a/plugins/quickopen/projectitemquickopen.h +++ b/plugins/quickopen/projectitemquickopen.h @@ -1,123 +1,123 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 PROJECT_ITEM_QUICKOPEN #define PROJECT_ITEM_QUICKOPEN #include "duchainitemquickopen.h" #include #include #include #include template class ResultCache { public: ResultCache(std::function func) : m_func(func) { } /// Mark this cache dirty. A call to cachedResult() will need to refill the cache inline void markDirty() const { m_isDirty = true; } /** * If marked dirty, calls @p func and stores return value of @p func * * @return Cached result of @p func */ inline Type cachedResult() const { if (m_isDirty) { m_result = m_func(); m_isDirty = false; } return m_result; } private: std::function m_func; mutable Type m_result; mutable bool m_isDirty = true; }; struct CodeModelViewItem { CodeModelViewItem() { } CodeModelViewItem(const KDevelop::IndexedString& file, const KDevelop::QualifiedIdentifier& id) : m_file(file) , m_id(id) { } KDevelop::IndexedString m_file; KDevelop::QualifiedIdentifier m_id; }; Q_DECLARE_TYPEINFO(CodeModelViewItem, Q_MOVABLE_TYPE); -typedef QMap > AddedItems; +using AddedItems = QMap>; class ProjectItemDataProvider : public KDevelop::QuickOpenDataProviderBase { Q_OBJECT public: enum ItemTypes { NoItems = 0, Classes = 1, Functions = 2, AllItemTypes = Classes + Functions }; explicit ProjectItemDataProvider(KDevelop::IQuickOpen* quickopen); void enableData(const QStringList& items, const QStringList& scopes) override; void setFilterText(const QString& text) override; void reset() override; uint itemCount() const override; uint unfilteredItemCount() const override; static QStringList supportedItemTypes(); private: KDevelop::QuickOpenDataPointer data(uint pos) const override; ItemTypes m_itemTypes; KDevelop::IQuickOpen* m_quickopen; QSet m_files; QVector m_currentItems; QString m_currentFilter; QVector m_filteredItems; //Maps positions to the additional items behind those positions //Here additional inserted items are stored, that are not represented in m_filteredItems. //This is needed at least to also show overloaded function declarations mutable AddedItems m_addedItems; ResultCache m_addedItemsCountCache; }; #endif diff --git a/plugins/quickopen/quickopenmodel.h b/plugins/quickopen/quickopenmodel.h index d9b79b7a25..38ab90cc82 100644 --- a/plugins/quickopen/quickopenmodel.h +++ b/plugins/quickopen/quickopenmodel.h @@ -1,133 +1,133 @@ /* This file is part of the KDE libraries Copyright (C) 2007 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 KDEVPLATFORM_PLUGIN_QUICKOPENMODEL_H #define KDEVPLATFORM_PLUGIN_QUICKOPENMODEL_H #include #include #include #include #include #include "expandingtree/expandingwidgetmodel.h" class QuickOpenModel : public ExpandingWidgetModel { Q_OBJECT public: explicit QuickOpenModel(QWidget* parent); void registerProvider(const QStringList& scopes, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider); /** * Remove provider. * @param provider The provider to remove * @return Whether a provider was removed. If false, the provider was not attached. * */ bool removeProvider(KDevelop::QuickOpenDataProviderBase* provider); ///Returns a list of all scopes that a registered through some providers QStringList allScopes() const; ///Returns a list of all types that a registered through some providers QStringList allTypes() const; /** * @param items The list of items that should be used. * @param scopes The list of scopes that should be used. * When this is called, the state is restart()ed. * */ void enableProviders(const QStringList& items, const QStringList& scopes); ///Reset all providers, unexpand everything, empty caches. void restart(bool keepFilterText = false); QModelIndex index(int, int, const QModelIndex& parent) const override; QModelIndex parent(const QModelIndex&) const override; int rowCount(const QModelIndex&) const override; int unfilteredRowCount() const; int columnCount() const; int columnCount(const QModelIndex&) const override; QVariant data(const QModelIndex&, int) const override; /** * Tries to execute the item currently selected. * Returns true if the quickopen-dialog should be closed. * @param filterText Should be the current content of the filter line-edit. * * If this returns false, and filterText was changed, the change must be put * into the line-edit. That way items may execute by changing the content * of the line-edit. * */ bool execute(const QModelIndex& index, QString& filterText); //The expandingwidgetmodel needs access to the tree-view void setTreeView(QTreeView* view); QTreeView* treeView() const override; virtual QSet fileSet() const; ///This value will be added to the height of all created expanding-widgets void setExpandingWidgetHeightIncrease(int pixels); public Q_SLOTS: void textChanged(const QString& str); private Q_SLOTS: void destroyed(QObject* obj); void resetTimer(); void restart_internal(bool keepFilterText); private: bool indexIsItem(const QModelIndex& index) const override; int contextMatchQuality(const QModelIndex& index) const override; KDevelop::QuickOpenDataPointer getItem(int row, bool noReset = false) const; - typedef QHash DataList; + using DataList = QHash; mutable DataList m_cachedData; QTreeView* m_treeView; QTimer* m_resetTimer; struct ProviderEntry { ProviderEntry() { } bool enabled = false; QSet scopes; QSet types; KDevelop::QuickOpenDataProviderBase* provider; }; - //typedef QMultiMap< QString, ProviderEntry > ProviderMap; + //using ProviderMap = QMultiMap; using ProviderList = QVector; ProviderList m_providers; QString m_filterText; int m_expandingWidgetHeightIncrease; mutable int m_resetBehindRow; QSet m_enabledItems; QSet m_enabledScopes; }; #endif