diff --git a/kdevplatform/CMakeLists.txt b/kdevplatform/CMakeLists.txt --- a/kdevplatform/CMakeLists.txt +++ b/kdevplatform/CMakeLists.txt @@ -63,6 +63,7 @@ add_subdirectory(util) add_subdirectory(outputview) add_subdirectory(vcs) +add_subdirectory(services) add_subdirectory(pics) add_subdirectory(debugger) add_subdirectory(documentation) diff --git a/kdevplatform/interfaces/context.h b/kdevplatform/interfaces/context.h --- a/kdevplatform/interfaces/context.h +++ b/kdevplatform/interfaces/context.h @@ -99,7 +99,8 @@ CodeContext, /** + + 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_IPROJECTSERVICEHOOKUP_H +#define KDEVPLATFORM_IPROJECTSERVICEHOOKUP_H + +#include "servicesexport.h" + +#include +#include + +class QWidget; + +namespace KDevelop +{ + +class IProject; +class IProjectServiceHookupConfiguration; +class ProjectServiceHookupConfigPage; + +/** + * Provides hook-up of projects with a service related to the projects. + * Examples are web services like issue tracker, task boards, repo browsers, + * communication channels, build servers. The actual service capabilities + * are implemented by other specific interfaces. + */ +class KDEVPLATFORMSERVICES_EXPORT IProjectServiceHookup +{ +public: + virtual ~IProjectServiceHookup(); + + /** + * @return a unique identifier for the project service, + */ + virtual QString projectServiceId() const = 0; + + /** + * Gets the user-visible string for the service type + * + * @return a translated user-visible name for the service type + */ + virtual QString projectServiceDisplayName() const = 0; + + /** + * Gets the icon for the UI representing the service type. + * @return an icon suitable for display in the UI + */ + virtual QIcon projectServiceIcon() const = 0; + + /** + * Loads the hookup configurations for the given @p project and the service this plugin supports. + * @param project the project which the configurations are for + * @return list of configuration objects, ownership is passed to the caller + */ + virtual QList loadProjectServiceHookupConfigurations(IProject* project) const = 0; + + /** + * Stores the hookup configurations for the given @p project with the service this plugin supports. + * @param projectServiceHookupConfigurations list of all configuration objects to save, ownership is not passed + * @param project the project which the configurations are for + */ + virtual void saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + IProject* project) = 0; + + /** + * Generates an empty hookup configuration object for the given @p project with the service this plugin supports. + * The configuration is not stored yet, for that it needs to be passed to + * @c saveProjectServiceHookupConfigurations() with all other existing configuration objects. + * The id and the displayName property can be preset with default data, but will be adapted by the caller. + * @param project the project which the configuration is for + * @return a new empty configuration object, ownership is passed to the caller + */ + virtual IProjectServiceHookupConfiguration* createProjectServiceHookupConfiguration(IProject* project) const = 0; + + /** + * Creates a widget for editing a hookup configuration of a project with the service this plugin supports. + * + * The @c ProjectServiceHookupConfigPage subclass of the returned widget instance needs to support + * the matching @c IProjectServiceHookupConfiguration subclass supported in the rest of this interface. + * + * @param parent the parent widget for the newly created widget + * @return a new widget object to edit a hookup configuration, ownership is passed to the caller. + */ + virtual ProjectServiceHookupConfigPage* createProjectServiceHookupConfigPage(QWidget* parent) const = 0; +}; + +} + +Q_DECLARE_INTERFACE(KDevelop::IProjectServiceHookup, "org.kdevelop.IProjectServiceHookup") + +#endif // KDEVPLATFORM_IPROJECTSERVICEHOOKUP_H diff --git a/kdevplatform/services/interfaces/iprojectservicehookup.cpp b/kdevplatform/services/interfaces/iprojectservicehookup.cpp new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/iprojectservicehookup.cpp @@ -0,0 +1,24 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "iprojectservicehookup.h" + +using namespace KDevelop; + +IProjectServiceHookup::~IProjectServiceHookup() = default; diff --git a/kdevplatform/services/interfaces/iprojectservicehookupconfiguration.h b/kdevplatform/services/interfaces/iprojectservicehookupconfiguration.h new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/iprojectservicehookupconfiguration.h @@ -0,0 +1,75 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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_IPROJECTSERVICEHOOKUPCONFIGURATION_H +#define KDEVPLATFORM_IPROJECTSERVICEHOOKUPCONFIGURATION_H + +#include "servicesexport.h" + +class QString; + +namespace KDevelop +{ + +/** + * Interface for configuration for a hook-up of a project to a service. + * Used for passing the config data in the working memory in an abstract way + * between the plugin providing the service hook up, thus implementing @c IProjectServiceHookup, + * and the config editing pages, being matching @c ProjectServiceHookupConfigPage subclasses. + */ +class KDEVPLATFORMSERVICES_EXPORT IProjectServiceHookupConfiguration +{ +public: + virtual ~IProjectServiceHookupConfiguration(); + + /** + * Provide a unique identifier for the given configuration of a + * hook up between a service and a project. + * Is e.g. used for the config group in the hookup configurations storage. + * @return a unique identifier for this service hookup configuration + */ + virtual QString id() const = 0; + + /** + * Sets unique identifier for the given configuration. + * @param id the new id + */ + virtual void setId(const QString& id) = 0; + + /** + * The user-chosen name for the project service configuration. + * @return the display name for the project service configuration + */ + virtual QString displayName() const = 0; + + /** + * Sets the user-chosen name for the project service configuration. + * @param displayName the new display name + */ + virtual void setDisplayName(const QString& displayName) = 0; + + /** + * @return the id of the project service hookup this configuration is for + */ + virtual QString projectServiceId() const = 0; +}; + +} + +#endif diff --git a/kdevplatform/services/interfaces/iprojectservicehookupconfiguration.cpp b/kdevplatform/services/interfaces/iprojectservicehookupconfiguration.cpp new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/iprojectservicehookupconfiguration.cpp @@ -0,0 +1,24 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "iprojectservicehookupconfiguration.h" + +using namespace KDevelop; + +IProjectServiceHookupConfiguration::~IProjectServiceHookupConfiguration() = default; diff --git a/kdevplatform/services/interfaces/iprojectserviceurlmapper.h b/kdevplatform/services/interfaces/iprojectserviceurlmapper.h new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/iprojectserviceurlmapper.h @@ -0,0 +1,88 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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_IPROJECTSERVICEURLMAPPER_H +#define KDEVPLATFORM_IPROJECTSERVICEURLMAPPER_H + +#include "servicesexport.h" + +#include +#include +#include + +class QUrl; + +namespace KTextEditor +{ +class Cursor; +} + +namespace KDevelop +{ +class Path; +class IProject; +class VcsRevision; + +class ProjectServiceUrlData +{ +public: + QUrl url; + QIcon icon; + QString name; +}; + +/** + * Provides a mapping between urls to locations/items in services and locations/items in the local project + */ +class KDEVPLATFORMSERVICES_EXPORT IProjectServiceUrlMapper +{ +public: + virtual ~IProjectServiceUrlMapper(); + + // TODO: to/from variants per mapping, perhaps split into separate interfaces? + enum ProjectServiceUrlMapperCapability { + TextSelectionProjectUrlMapping = 0x1 << 0, + VcsRevisionProjectUrlMapping = 0x1 << 1, + VcsRevisionFileUrlMapping = 0x1 << 2 + }; + Q_DECLARE_FLAGS(ProjectServiceUrlMapperCapabilities, ProjectServiceUrlMapperCapability) + + virtual ProjectServiceUrlMapperCapabilities projectServiceUrlMapperCapabilities() const = 0; + + /** @returns the url of the service matching the document at the project url with the cursor */ + virtual QVector projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, IProject* project) const; + + // TODO: + // virtual QUrl urlFromWebServiceUrl(const QUrl& url, IProject* project) const = 0; + + virtual QVector projectServiceUrlsFromVcsRevision(const VcsRevision& revision, + IProject* project) const; + + virtual QVector projectServiceUrlsFromVcsRevision(const QUrl& url, + const VcsRevision& revision, + IProject* project) const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(IProjectServiceUrlMapper::ProjectServiceUrlMapperCapabilities) + +} + +Q_DECLARE_INTERFACE(KDevelop::IProjectServiceUrlMapper, "org.kdevelop.IProjectServiceUrlMapper") + +#endif // KDEVPLATFORM_IPROJECTSERVICEURLMAPPER_H diff --git a/kdevplatform/services/interfaces/iprojectserviceurlmapper.cpp b/kdevplatform/services/interfaces/iprojectserviceurlmapper.cpp new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/iprojectserviceurlmapper.cpp @@ -0,0 +1,56 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "iprojectserviceurlmapper.h" + +using namespace KDevelop; + +IProjectServiceUrlMapper::~IProjectServiceUrlMapper() = default; + +QVector +IProjectServiceUrlMapper::projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, + IProject* project) const +{ + Q_UNUSED(url); + Q_UNUSED(cursor); + Q_UNUSED(project); + + return {}; +} + +QVector +IProjectServiceUrlMapper::projectServiceUrlsFromVcsRevision(const VcsRevision& revision, + IProject* project) const +{ + Q_UNUSED(revision); + Q_UNUSED(project); + + return {}; +} + +QVector +IProjectServiceUrlMapper::projectServiceUrlsFromVcsRevision(const QUrl& url, const VcsRevision& revision, + IProject* project) const +{ + Q_UNUSED(url); + Q_UNUSED(revision); + Q_UNUSED(project); + + return {}; +} diff --git a/kdevplatform/services/interfaces/projectservicehookupconfigpage.h b/kdevplatform/services/interfaces/projectservicehookupconfigpage.h new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/projectservicehookupconfigpage.h @@ -0,0 +1,68 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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_SERVICEHOOKUPCONFIGPAGE_H +#define KDEVPLATFORM_SERVICEHOOKUPCONFIGPAGE_H + +#include "servicesexport.h" + +#include + +class KConfigGroup; + +namespace KDevelop +{ + +class IProject; +class IProjectServiceHookupConfiguration; + +/** + * Provides a configuration page for a project service hookup configuration, the interface + * allows the actual dialog to easily load/save the configuration and show some title/icon + */ +class KDEVPLATFORMSERVICES_EXPORT ProjectServiceHookupConfigPage : public QWidget +{ + Q_OBJECT + +public: + explicit ProjectServiceHookupConfigPage(QWidget* parent); + ~ProjectServiceHookupConfigPage() override; + + /** + * Allows the page to load values from an existing service hookup configuration object + * @param configuration the configuration to load from + */ + virtual void loadFromConfiguration(const IProjectServiceHookupConfiguration* configuration) = 0; + + /** + * Allows the page to save values to an existing service hookup configuration object + * @param configuration the configuration to save to + */ + virtual void saveToConfiguration(IProjectServiceHookupConfiguration* configuration) const = 0; + +Q_SIGNALS: + /** + * Emitted if the user did any editing to the loaded configuration data. + */ + void changed(); +}; + +} + +#endif diff --git a/kdevplatform/services/interfaces/projectservicehookupconfigpage.cpp b/kdevplatform/services/interfaces/projectservicehookupconfigpage.cpp new file mode 100644 --- /dev/null +++ b/kdevplatform/services/interfaces/projectservicehookupconfigpage.cpp @@ -0,0 +1,29 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "projectservicehookupconfigpage.h" + +using namespace KDevelop; + +ProjectServiceHookupConfigPage::ProjectServiceHookupConfigPage(QWidget* parent) + : QWidget(parent) +{ +} + +ProjectServiceHookupConfigPage::~ProjectServiceHookupConfigPage() = default; diff --git a/kdevplatform/vcs/CMakeLists.txt b/kdevplatform/vcs/CMakeLists.txt --- a/kdevplatform/vcs/CMakeLists.txt +++ b/kdevplatform/vcs/CMakeLists.txt @@ -23,6 +23,7 @@ vcsdiff.cpp vcsevent.cpp vcsstatusinfo.cpp + vcsrevisioncontext.cpp widgets/vcsimportmetadatawidget.cpp widgets/vcseventwidget.cpp widgets/vcsdiffwidget.cpp @@ -72,6 +73,7 @@ vcsevent.h vcsstatusinfo.h vcslocation.h + vcsrevisioncontext.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/vcs COMPONENT Devel ) diff --git a/kdevplatform/vcs/vcspluginhelper.cpp b/kdevplatform/vcs/vcspluginhelper.cpp --- a/kdevplatform/vcs/vcspluginhelper.cpp +++ b/kdevplatform/vcs/vcspluginhelper.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include "interfaces/idistributedversioncontrol.h" @@ -438,6 +439,26 @@ connect(historyAction, &QAction::triggered, this, [this, rev]() { history(rev); }); + + // TODO: somehow on repeated context menu showing ctxUrls is empty, find why + if (!d->ctxUrls.isEmpty()) { + IProject* project = ICore::self()->projectController()->findProjectForUrl(d->ctxUrls.first()); + if (project ) { + QUrl url; + if (d->ctxUrls.count() == 1) { + url = d->ctxUrls.at(0); + } + VcsRevisionContext context(project, rev, url); + const auto extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions(&context, menu); + + QList vcsActions; + for (const auto& ext : extensions) { + // TODO: git submenu always added for all context type, needs another approach + vcsActions += ext.actions(ContextMenuExtension::VcsGroup); + } + menu->addActions(vcsActions); + } + } } void VcsPluginHelper::handleAnnotationBorderVisibilityChanged(View* view, bool visible) diff --git a/kdevplatform/vcs/vcsrevisioncontext.h b/kdevplatform/vcs/vcsrevisioncontext.h new file mode 100644 --- /dev/null +++ b/kdevplatform/vcs/vcsrevisioncontext.h @@ -0,0 +1,64 @@ +/* This file is part of KDevelop + * + * Copyright 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef KDEVPLATFORM_VCSREVISIONCONTEXT_H +#define KDEVPLATFORM_VCSREVISIONCONTEXT_H + +#include + +#include + +namespace KDevelop +{ +class VcsRevision; +class IProject; + +/** + * A context for a VcsRevision. + */ +class KDEVPLATFORMVCS_EXPORT VcsRevisionContext : public Context +{ +public: + /**Builds the context. + @param revision The items to build the context from.*/ + explicit VcsRevisionContext(IProject* project, const VcsRevision& revision, const QUrl& url = {}); + + /**Destructor.*/ + ~VcsRevisionContext() override; + + int type() const override; + + QList urls() const override; + + IProject* project() const; + + /** + * @return the revisipon. + */ + VcsRevision revision() const; + +private: + const QScopedPointer d; + Q_DISABLE_COPY(VcsRevisionContext) +}; + +} + +#endif diff --git a/kdevplatform/vcs/vcsrevisioncontext.cpp b/kdevplatform/vcs/vcsrevisioncontext.cpp new file mode 100644 --- /dev/null +++ b/kdevplatform/vcs/vcsrevisioncontext.cpp @@ -0,0 +1,71 @@ +/* This file is part of KDevelop + * + * Copyright 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "vcsrevisioncontext.h" + +#include "vcsrevision.h" + +using namespace KDevelop; + +class KDevelop::VcsRevisionContextPrivate +{ +public: + VcsRevisionContextPrivate(IProject* project, const VcsRevision& revision, const QUrl& url) + : project(project) + , revision(revision) + , url(url) + {} + + IProject* const project; + const VcsRevision revision; + const QUrl url; +}; + +VcsRevisionContext::VcsRevisionContext(IProject* project, const VcsRevision& revision, const QUrl& url) + : Context() + , d(new VcsRevisionContextPrivate(project, revision, url)) +{ +} + +VcsRevisionContext::~VcsRevisionContext() = default; + +int VcsRevisionContext::type() const +{ + return Context::VcsRevisionContext; +} + +QList VcsRevisionContext::urls() const +{ + if (d->url.isValid()) { + return { d->url }; + } + + return {}; +} + +IProject* KDevelop::VcsRevisionContext::project() const +{ + return d->project; +} + +VcsRevision VcsRevisionContext::revision() const +{ + return d->revision; +} diff --git a/kdevplatform/vcs/widgets/vcseventwidget.cpp b/kdevplatform/vcs/widgets/vcseventwidget.cpp --- a/kdevplatform/vcs/widgets/vcseventwidget.cpp +++ b/kdevplatform/vcs/widgets/vcseventwidget.cpp @@ -33,6 +33,10 @@ #include #include +#include +#include +#include +#include #include "ui_vcseventwidget.h" #include "vcsdiffwidget.h" @@ -43,6 +47,7 @@ #include "../vcsevent.h" #include "../vcsjob.h" #include "../vcsrevision.h" +#include "../vcsrevisioncontext.h" #include "debug.h" @@ -94,6 +99,21 @@ QObject::connect(diffRevisionsAction, &QAction::triggered, q, [&] { diffRevisions(); }); diffRevisionsAction->setEnabled(m_ui->eventView->selectionModel()->selectedRows().size()>=2); + IProject* project = ICore::self()->projectController()->findProjectForUrl(m_url); + if (project ) { + const auto revision = m_logModel->eventForIndex(m_contextIndex).revision(); + if (revision.revisionType() != KDevelop::VcsRevision::Invalid) { + VcsRevisionContext context(project, revision, m_url); + const auto extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions(&context, &menu); + + QList vcsActions; + for (const auto& ext : extensions) { + // TODO: git submenu always added for all context type, needs another approach + vcsActions += ext.actions(ContextMenuExtension::VcsGroup); + } + menu.addActions(vcsActions); + } + } menu.exec( m_ui->eventView->viewport()->mapToGlobal(point) ); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -98,6 +98,14 @@ add_subdirectory(vcschangesview) # END: VCS +# BEGIN: Project services +add_subdirectory(projectservicemanager) +add_subdirectory(projectserviceurlmapper) +add_subdirectory(cgit) +add_subdirectory(gitlab) +add_subdirectory(phabricator) +# END: Project services + # BEGIN: Others add_subdirectory(appwizard) add_subdirectory(codeutils) diff --git a/plugins/cgit/CMakeLists.txt b/plugins/cgit/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/cgit/CMakeLists.txt @@ -0,0 +1,24 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevcgit\") + +add_subdirectory(icons) + +set(kdevcgit_SRCS + projectvcstracker.cpp + cgitplugin.cpp + cgithookupconfiguration.cpp + cgithookupconfigpage.cpp +) + +ki18n_wrap_ui(kdevcgit_SRCS + cgithookupconfigpage.ui +) + +kdevplatform_add_plugin(kdevcgit + JSON cgitplugin.json + SOURCES ${kdevcgit_SRCS} +) + +target_link_libraries(kdevcgit + KDev::Vcs + KDev::Services +) diff --git a/plugins/cgit/Messages.sh b/plugins/cgit/Messages.sh new file mode 100644 --- /dev/null +++ b/plugins/cgit/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevcgit.pot +rm -f rc.cpp diff --git a/plugins/cgit/cgithookupconfigpage.h b/plugins/cgit/cgithookupconfigpage.h new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgithookupconfigpage.h @@ -0,0 +1,45 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 CGITHOOKUPCONFIGPAGE_H +#define CGITHOOKUPCONFIGPAGE_H + +#include + +namespace Ui +{ +class CgitHookupConfigPage; +} + +class CgitHookupConfigPage : public KDevelop::ProjectServiceHookupConfigPage +{ + Q_OBJECT + +public: + explicit CgitHookupConfigPage(QWidget* parent); + ~CgitHookupConfigPage() override; + + void loadFromConfiguration(const KDevelop::IProjectServiceHookupConfiguration* configuration) override; + void saveToConfiguration(KDevelop::IProjectServiceHookupConfiguration* configuration) const override; + +private: + QScopedPointer m_ui; +}; + +#endif // CGITHOOKUPCONFIGPAGE_H diff --git a/plugins/cgit/cgithookupconfigpage.cpp b/plugins/cgit/cgithookupconfigpage.cpp new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgithookupconfigpage.cpp @@ -0,0 +1,48 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "cgithookupconfigpage.h" +#include "ui_cgithookupconfigpage.h" + +#include "cgithookupconfiguration.h" + +using namespace KDevelop; + +CgitHookupConfigPage::CgitHookupConfigPage(QWidget* parent) + : ProjectServiceHookupConfigPage(parent) + , m_ui(new Ui::CgitHookupConfigPage) +{ + m_ui->setupUi(this); + connect(m_ui->baseUrlEdit, &QLineEdit::textEdited, this, &CgitHookupConfigPage::changed); +} + +CgitHookupConfigPage::~CgitHookupConfigPage() = default; + + +void CgitHookupConfigPage::loadFromConfiguration(const IProjectServiceHookupConfiguration* configuration) +{ + const CgitHookupConfiguration* cgitHookupConfiguration = dynamic_cast(configuration); + m_ui->baseUrlEdit->setText(cgitHookupConfiguration->baseUrl().toString()); +} + +void CgitHookupConfigPage::saveToConfiguration(IProjectServiceHookupConfiguration* configuration) const +{ + CgitHookupConfiguration* cgitHookupConfiguration = dynamic_cast(configuration); + cgitHookupConfiguration->setBaseUrl(QUrl::fromUserInput(m_ui->baseUrlEdit->text())); +} diff --git a/plugins/cgit/cgithookupconfigpage.ui b/plugins/cgit/cgithookupconfigpage.ui new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgithookupconfigpage.ui @@ -0,0 +1,38 @@ + + + CgitHookupConfigPage + + + + 0 + 0 + 198 + 29 + + + + + 0 + + + + + Base URL: + + + + + + + URL of the index page (https://host/path/project.git) + + + true + + + + + + + + diff --git a/plugins/cgit/cgithookupconfiguration.h b/plugins/cgit/cgithookupconfiguration.h new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgithookupconfiguration.h @@ -0,0 +1,48 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 CGITHOOKUPCONFIGURATION_H +#define CGITHOOKUPCONFIGURATION_H + +#include + +#include +#include + +class CgitHookupConfiguration : public KDevelop::IProjectServiceHookupConfiguration +{ +public: + CgitHookupConfiguration(const QString& id, const QString& name); + + QString id() const override; + void setId(const QString& id) override; + QString displayName() const override; + void setDisplayName(const QString& displayName) override; + QString projectServiceId() const override; + + QUrl baseUrl() const; + void setBaseUrl(const QUrl& baseUrl); + +private: + QString m_id; + QString m_name; + QUrl m_baseUrl; +}; + +#endif // CGITHOOKUPCONFIGURATION_H diff --git a/plugins/cgit/cgithookupconfiguration.cpp b/plugins/cgit/cgithookupconfiguration.cpp new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgithookupconfiguration.cpp @@ -0,0 +1,61 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "cgithookupconfiguration.h" + +CgitHookupConfiguration::CgitHookupConfiguration(const QString& id, const QString& name) + : m_id(id) + , m_name(name) +{ +} + +QString CgitHookupConfiguration::id() const +{ + return m_id; +} + +void CgitHookupConfiguration::setId(const QString& id) +{ + m_id = id; +} + +QString CgitHookupConfiguration::displayName() const +{ + return m_name; +} + +void CgitHookupConfiguration::setDisplayName(const QString& displayName) +{ + m_name = displayName; +} + +QString CgitHookupConfiguration::projectServiceId() const +{ + return QStringLiteral("cgit"); +} + +void CgitHookupConfiguration::setBaseUrl(const QUrl& baseUrl) +{ + m_baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash); +} + +QUrl CgitHookupConfiguration::baseUrl() const +{ + return m_baseUrl; +} diff --git a/plugins/cgit/cgitplugin.h b/plugins/cgit/cgitplugin.h new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgitplugin.h @@ -0,0 +1,82 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 CGITPLUGIN_H +#define CGITPLUGIN_H + +#include +#include +#include + +#include "projectvcstracker.h" + +class CgitPlugin : public KDevelop::IPlugin, + public KDevelop::IProjectServiceHookup, + public KDevelop::IProjectServiceUrlMapper +{ + Q_OBJECT + Q_INTERFACES( KDevelop::IProjectServiceHookup ) + Q_INTERFACES( KDevelop::IProjectServiceUrlMapper ) + +public: + /** + * Constructor with arguments as needed with KPluginFactory + * + * @param parent + * @param args + */ + CgitPlugin(QObject* parent, const QVariantList& args); + ~CgitPlugin() override; + +public: // IPlugin API + QString projectServiceId() const override; + QString projectServiceDisplayName() const override; + QIcon projectServiceIcon() const override; + +public: // IProjectServiceHookup API + QList loadProjectServiceHookupConfigurations(KDevelop::IProject* project) const override; + void saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + KDevelop::IProject* project) override; + KDevelop::IProjectServiceHookupConfiguration* createProjectServiceHookupConfiguration(KDevelop::IProject* project) const override; + + KDevelop::ProjectServiceHookupConfigPage* createProjectServiceHookupConfigPage(QWidget* parent) const override; + +public: // IProjectServiceUrlMapper API + ProjectServiceUrlMapperCapabilities projectServiceUrlMapperCapabilities() const override; + QVector + projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, + KDevelop::IProject* project) const override; + + QVector + projectServiceUrlsFromVcsRevision(const KDevelop::VcsRevision& revision, KDevelop::IProject* project) const override; + + QVector + projectServiceUrlsFromVcsRevision(const QUrl& url, const KDevelop::VcsRevision& revision, + KDevelop::IProject* project) const override; + +private: + QVector + urlsFromVcsRevision(const QString& relativeFilePath, const KDevelop::VcsRevision& revision, + KDevelop::IProject* project) const; + +private: + ProjectVcsTracker m_projectVcsTracker; +}; + +#endif // CGITPLUGIN_H diff --git a/plugins/cgit/cgitplugin.cpp b/plugins/cgit/cgitplugin.cpp new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgitplugin.cpp @@ -0,0 +1,236 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "cgitplugin.h" + +#include "cgithookupconfiguration.h" +#include "cgithookupconfigpage.h" + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace KDevelop; + +K_PLUGIN_FACTORY_WITH_JSON(CgitPluginFactory, "cgitplugin.json", registerPlugin();) + + +namespace { + +namespace Strings { +inline QString cgitGroupKey() { return QStringLiteral("cgit Settings"); } +inline QString nameKey() { return QStringLiteral("Name"); } +inline QString baseUrlKey() { return QStringLiteral("BaseUrl"); } +} + +} + + +CgitPlugin::CgitPlugin(QObject *parent, const QVariantList& args) + : KDevelop::IPlugin(QStringLiteral("kdevcgit"), parent) +{ + Q_UNUSED(args); + +} + +CgitPlugin::~CgitPlugin() = default; + + +QString CgitPlugin::projectServiceId() const +{ + return QStringLiteral("cgit"); +} + +QString CgitPlugin::projectServiceDisplayName() const +{ + return QStringLiteral("cgit"); +} + +QIcon CgitPlugin::projectServiceIcon() const +{ + return QIcon::fromTheme(QStringLiteral("cgit")); +} + +QList CgitPlugin::loadProjectServiceHookupConfigurations(IProject* project) const +{ + QList result; + + KConfigGroup cfg(project->projectConfiguration(), Strings::cgitGroupKey()); + + for (const auto& groupName : cfg.groupList()) { + KConfigGroup configurationGroup(&cfg, groupName); + + const QString name = configurationGroup.readEntry(Strings::nameKey(), QString()); + auto cgitConfiguration = new CgitHookupConfiguration(groupName, name); + const QUrl baseUrl = QUrl::fromUserInput(configurationGroup.readEntry(Strings::baseUrlKey(), QString())); + cgitConfiguration->setBaseUrl(baseUrl); + + result << cgitConfiguration; + } + + return result; +} + +void CgitPlugin::saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + IProject* project) +{ + QStringList configurationNames; + for (auto configuration : projectServiceHookupConfigurations) { + configurationNames.append(configuration->id()); + } + + KConfigGroup cfg(project->projectConfiguration(), Strings::cgitGroupKey()); + + // clean up no longer existing configurations + for (const auto& groupName : cfg.groupList()) { + if (!configurationNames.contains(groupName)) { + cfg.deleteGroup(groupName); + } + } + + // store current ones + for (auto configuration : projectServiceHookupConfigurations) { + auto cgitConfiguration = dynamic_cast(configuration); + Q_ASSERT(cgitConfiguration); + if (!cgitConfiguration) { + continue; + } + KConfigGroup configurationGroup(&cfg, configuration->id()); + configurationGroup.deleteGroup(); + + configurationGroup.writeEntry(Strings::nameKey(), cgitConfiguration->displayName()); + configurationGroup.writeEntry(Strings::baseUrlKey(), cgitConfiguration->baseUrl().toString()); + } + + cfg.sync(); +} + +IProjectServiceHookupConfiguration* CgitPlugin::createProjectServiceHookupConfiguration(IProject* project) const +{ + Q_UNUSED(project); + return new CgitHookupConfiguration(projectServiceId(), projectServiceDisplayName()); +} + +ProjectServiceHookupConfigPage* CgitPlugin::createProjectServiceHookupConfigPage(QWidget* parent) const +{ + return new CgitHookupConfigPage(parent); +} + +QVector CgitPlugin::projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, IProject* project) const +{ + const QString relativeFilePath = project->path().relativePath(Path(url)); + if (relativeFilePath.isEmpty() || relativeFilePath.startsWith(QLatin1String("../"))) { + return {}; + } + + QVector result; + const auto configurations = loadProjectServiceHookupConfigurations(project); + for (auto configuration : configurations) { + const auto cgitConfiguration = dynamic_cast(configuration); + + QUrl url = cgitConfiguration->baseUrl(); + if (!url.isValid()) { + continue; + } + + // TODO: get the remote tracked branch of the matching remote repo here instead + const QString branch = m_projectVcsTracker.currentBranchName(project); + if (branch.isEmpty()) { + continue; + } + + // TODO: find out what url encoding branch and relativeFilePath need + const QString path = url.path() + QLatin1String("/tree/") + relativeFilePath; + const QString query = QLatin1String("h=") + branch; + const QString fragment = QLatin1Char('n') + QString::number(cursor.line()+1); + url.setPath(path); + url.setQuery(query); + url.setFragment(fragment); + + ProjectServiceUrlData data; + data.url = url; + data.name = cgitConfiguration->displayName(); + data.icon = projectServiceIcon(); + result << data; + } + + return result; +} + +IProjectServiceUrlMapper::ProjectServiceUrlMapperCapabilities CgitPlugin::projectServiceUrlMapperCapabilities() const +{ + return TextSelectionProjectUrlMapping | VcsRevisionProjectUrlMapping | VcsRevisionFileUrlMapping; +} + +QVector CgitPlugin::projectServiceUrlsFromVcsRevision(const VcsRevision& revision, IProject* project) const +{ + return urlsFromVcsRevision(QString(), revision, project); +} + +QVector CgitPlugin::projectServiceUrlsFromVcsRevision(const QUrl& url, const VcsRevision& revision, IProject* project) const +{ + const QString relativeFilePath = project->path().relativePath(Path(url)); + if (relativeFilePath.isEmpty() || relativeFilePath.startsWith(QLatin1String("../"))) { + return {}; + } + + return urlsFromVcsRevision(relativeFilePath, revision, project); +} + +QVector CgitPlugin::urlsFromVcsRevision(const QString& relativeFilePath, const VcsRevision& revision, IProject* project) const +{ + QVector result; + const QString revisionHash = revision.revisionValue().toString(); + if (revisionHash.isEmpty()) { + return result; + } + + const auto configurations = loadProjectServiceHookupConfigurations(project); + for (auto configuration : configurations) { + const auto cgitConfiguration = dynamic_cast(configuration); + + QUrl url = cgitConfiguration->baseUrl(); + if (!url.isValid()) { + continue; + } + + // TODO: find out what url encoding branch and relativeFilePath need + const QString path = url.path() + QLatin1String("/commit/") + relativeFilePath; + const QString query = QLatin1String("id=") + revisionHash; + url.setPath(path); + url.setQuery(query); + + ProjectServiceUrlData data; + data.url = url; + data.name = cgitConfiguration->displayName(); + data.icon = projectServiceIcon(); + result << data; + } + + return result; +} + +// needed for QObject class created from K_PLUGIN_FACTORY_WITH_JSON +#include "cgitplugin.moc" diff --git a/plugins/cgit/cgitplugin.json b/plugins/cgit/cgitplugin.json new file mode 100644 --- /dev/null +++ b/plugins/cgit/cgitplugin.json @@ -0,0 +1,18 @@ +{ + "KPlugin": { + "Id": "kdevcgit", + "Name": "cgit Support", + "Description": "Enables integration of cgit web services", + "Icon": "cgit", + "Category": "Utilities", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Interfaces": [ + "org.kdevelop.IProjectServiceHookup", + "org.kdevelop.IProjectServiceUrlMapper" + ], + "X-KDevelop-Mode": "GUI" +} diff --git a/plugins/cgit/icons/96-apps-cgit.png b/plugins/cgit/icons/96-apps-cgit.png new file mode 100644 index 0000000000000000000000000000000000000000..50d8de5cb67352554438e65b9072869d404fcca6 GIT binary patch literal 1073 zc%17D@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wF4y>_XqfdxB|HiV6=*XVLq6g$-uCP zfnf=ly9_J>Bo~84fb6AUvnU_-a5TMP^;){=OQY`sxy)H6A#C~u|5l$nKj&vdirUIW3j#DmxLTX0 zc`i{(*vPnJwZI2^2KA3l%kyXLt@>y8Ant<5$)#13&iXbmIqiAFZQ(FuejO{DM17QH z{MJVf*JhsA)9SSjUiXgMH}A$?ldoAq4X+Z9e~s9D>`zN1eTub3n@JZoopuvvafomAfLyIM2+D$G?ICVt#}`q#Slu2TIjo#^Ma8=ma(v}AZ2 zcxZFK%{q0r^Rom5WHHE&hnt3KpT6q8Ji&3S z+bVPVyeKc3i2+;R>;8MwnX>9{op$-=@7>`NSL<>c#bwJ}X9T7CUJiLDQs2P1r*L-5 ztmOs`5^J{}V`lh!d-fjLOAIVM?ykquMBR!RxC+v@KKgF*<@u8Jd*!Z9-Jg1Hh1v3d zrWw()bw*F)A4Zp(&ssh4qp6A3$_-6U3xP59>irki3a(TotuGNwz?8t?>FVdQ&MBb@ E08XK35dZ)H literal 0 Hc$@ + Copyright 2017 Friedrich W. H. Kossebau + + 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 PROJECTVCSTRACKER_H +#define PROJECTVCSTRACKER_H + +#include +#include +#include + +class QUrl; + +namespace KDevelop { + class IProject; + class VcsJob; +} + +/** + * Keeps track of the current branches of the loaded projects + * to allow sync read access to the current branch name + * forked&adapted from plugins/projectmanagerview/vcsoverlayproxymodel + */ +class ProjectVcsTracker : public QObject +{ + Q_OBJECT + +public: + explicit ProjectVcsTracker(QObject* parent = nullptr); + + QString currentBranchName(KDevelop::IProject* project) const; + +private: + void startProjectTracking(KDevelop::IProject* project); + void stopProjectTracking(KDevelop::IProject* project); + void onBranchNameReady(KDevelop::VcsJob* job); +private Q_SLOTS: // needs to be tagged as slot for a string-based connect + void handleRepositoryBranchChange(const QUrl& url); + +private: + QSet m_pendingProjects; + QHash m_branchNamePerProject; +}; + +#endif // PROJECTVCSTRACKER_H diff --git a/plugins/cgit/projectvcstracker.cpp b/plugins/cgit/projectvcstracker.cpp new file mode 100644 --- /dev/null +++ b/plugins/cgit/projectvcstracker.cpp @@ -0,0 +1,139 @@ +/* This file is part of KDevelop + Copyright 2013 Aleix Pol + Copyright 2017 Friedrich W. H. Kossebau + + 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 "projectvcstracker.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace KDevelop; + +using SafeProjectPointer = QPointer; +Q_DECLARE_METATYPE(SafeProjectPointer) + +ProjectVcsTracker::ProjectVcsTracker(QObject* parent) + : QObject(parent) +{ + auto projectController = ICore::self()->projectController(); + + connect(projectController, &IProjectController::projectOpened, + this, &ProjectVcsTracker::startProjectTracking); + connect(projectController, &IProjectController::projectClosing, + this, &ProjectVcsTracker::stopProjectTracking); + + for (const auto project : projectController->projects()) { + startProjectTracking(project); + } +} + +QString ProjectVcsTracker::currentBranchName(IProject* project) const +{ + return m_branchNamePerProject.value(project); +} + +void ProjectVcsTracker::startProjectTracking(IProject* project) +{ + IPlugin* plugin = project->versionControlPlugin(); + if (!plugin) { + return; + } + + // TODO: Show revision in case we're in "detached" state + IBranchingVersionControl* branchingExtension = plugin->extension(); + if (branchingExtension) { + const QUrl url = project->path().toUrl(); + branchingExtension->registerRepositoryForCurrentBranchChanges(url); + //can't use new signal/slot syntax here, IBranchingVersionControl is not a QObject + connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), SLOT(handleRepositoryBranchChange(QUrl))); + handleRepositoryBranchChange(url); + } +} + +void ProjectVcsTracker::handleRepositoryBranchChange(const QUrl& url) +{ + const auto allProjects = ICore::self()->projectController()->projects(); + for (IProject* project : allProjects) { + const QUrl projectUrl = project->path().toUrl(); + const bool isExactMatch = url.matches(projectUrl, QUrl::StripTrailingSlash); + const bool isParentOf = url.isParentOf(projectUrl); + if (isParentOf || isExactMatch) { + // example projects in KDevelop: + // - /path/to/mygitrepo/: isParentOf=0 isExactMatch=1, + // - /path/to/mygitrepo/myproject: isParentOf=1 isExactMatch=0 + // - /path/to/norepo: isParentOf=0 isExactMatch=0 + // isParentOf=1 isExactMatch=1 is not a valid combination + + IPlugin* v = project->versionControlPlugin(); + Q_ASSERT(!isExactMatch || v); // project url == 'change' url => project should be associated with a VCS plugin + if (!v) { + continue; + } + + IBranchingVersionControl* branching = v->extension(); + Q_ASSERT(branching); + VcsJob* job = branching->currentBranch(url); + connect(job, &VcsJob::resultsReady, this, &ProjectVcsTracker::onBranchNameReady); + // pass the current project instance as QPointer, to track if this very instance + // is still alive when the job is done. this protects against the rare chance + // that another project instance reuses the same address and wrongly would be + // matched by just comparing pointers + job->setProperty("project", QVariant::fromValue(project)); + ICore::self()->runController()->registerJob(job); + if (!m_branchNamePerProject.contains(project) && !m_pendingProjects.contains(project)) { + m_pendingProjects.insert(project); + } + } + } +} + +void ProjectVcsTracker::onBranchNameReady(VcsJob* job) +{ + if (job->status() == VcsJob::JobSucceeded) { + SafeProjectPointer p = job->property("project").value(); + IProject* project = p.data(); + if (project) { + const auto branchName = job->fetchResults().toString(); + + if (m_pendingProjects.contains(project)) { + m_pendingProjects.remove(project); + m_branchNamePerProject.insert(project, branchName); + } else { + auto it = m_branchNamePerProject.find(project); + if (it != m_branchNamePerProject.end()) { + it.value() = branchName; + } + } + } + } +} + +void ProjectVcsTracker::stopProjectTracking(IProject* project) +{ + m_pendingProjects.remove(project); + m_branchNamePerProject.remove(project); +} diff --git a/plugins/gitlab/CMakeLists.txt b/plugins/gitlab/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/gitlab/CMakeLists.txt @@ -0,0 +1,24 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevgitlab\") + +add_subdirectory(icons) + +set(kdevgitlab_SRCS + projectvcstracker.cpp + gitlabplugin.cpp + gitlabhookupconfiguration.cpp + gitlabhookupconfigpage.cpp +) + +ki18n_wrap_ui(kdevgitlab_SRCS + gitlabhookupconfigpage.ui +) + +kdevplatform_add_plugin(kdevgitlab + JSON gitlabplugin.json + SOURCES ${kdevgitlab_SRCS} +) + +target_link_libraries(kdevgitlab + KDev::Vcs + KDev::Services +) diff --git a/plugins/gitlab/Messages.sh b/plugins/gitlab/Messages.sh new file mode 100644 --- /dev/null +++ b/plugins/gitlab/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevgitlab.pot +rm -f rc.cpp diff --git a/plugins/gitlab/gitlabhookupconfigpage.h b/plugins/gitlab/gitlabhookupconfigpage.h new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabhookupconfigpage.h @@ -0,0 +1,45 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 GITLABHOOKUPCONFIGPAGE_H +#define GITLABHOOKUPCONFIGPAGE_H + +#include + +namespace Ui +{ +class GitLabHookupConfigPage; +} + +class GitLabHookupConfigPage : public KDevelop::ProjectServiceHookupConfigPage +{ + Q_OBJECT + +public: + explicit GitLabHookupConfigPage(QWidget* parent); + ~GitLabHookupConfigPage() override; + + void loadFromConfiguration(const KDevelop::IProjectServiceHookupConfiguration* configuration) override; + void saveToConfiguration(KDevelop::IProjectServiceHookupConfiguration* configuration) const override; + +private: + QScopedPointer m_ui; +}; + +#endif // GITLABHOOKUPCONFIGPAGE_H diff --git a/plugins/gitlab/gitlabhookupconfigpage.cpp b/plugins/gitlab/gitlabhookupconfigpage.cpp new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabhookupconfigpage.cpp @@ -0,0 +1,48 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "gitlabhookupconfigpage.h" +#include "ui_gitlabhookupconfigpage.h" + +#include "gitlabhookupconfiguration.h" + +using namespace KDevelop; + +GitLabHookupConfigPage::GitLabHookupConfigPage(QWidget* parent) + : ProjectServiceHookupConfigPage(parent) + , m_ui(new Ui::GitLabHookupConfigPage) +{ + m_ui->setupUi(this); + connect(m_ui->baseUrlEdit, &QLineEdit::textEdited, this, &GitLabHookupConfigPage::changed); +} + +GitLabHookupConfigPage::~GitLabHookupConfigPage() = default; + + +void GitLabHookupConfigPage::loadFromConfiguration(const IProjectServiceHookupConfiguration* configuration) +{ + const GitLabHookupConfiguration* gitlabHookupConfiguration = dynamic_cast(configuration); + m_ui->baseUrlEdit->setText(gitlabHookupConfiguration->baseUrl().toString()); +} + +void GitLabHookupConfigPage::saveToConfiguration(IProjectServiceHookupConfiguration* configuration) const +{ + GitLabHookupConfiguration* gitlabHookupConfiguration = dynamic_cast(configuration); + gitlabHookupConfiguration->setBaseUrl(QUrl::fromUserInput(m_ui->baseUrlEdit->text())); +} diff --git a/plugins/gitlab/gitlabhookupconfigpage.ui b/plugins/gitlab/gitlabhookupconfigpage.ui new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabhookupconfigpage.ui @@ -0,0 +1,38 @@ + + + GitLabHookupConfigPage + + + + 0 + 0 + 198 + 29 + + + + + 0 + + + + + Base URL: + + + + + + + URL of the index page (https://host/path/project) + + + true + + + + + + + + diff --git a/plugins/gitlab/gitlabhookupconfiguration.h b/plugins/gitlab/gitlabhookupconfiguration.h new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabhookupconfiguration.h @@ -0,0 +1,48 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 GITLABHOOKUPCONFIGURATION_H +#define GITLABHOOKUPCONFIGURATION_H + +#include + +#include +#include + +class GitLabHookupConfiguration : public KDevelop::IProjectServiceHookupConfiguration +{ +public: + GitLabHookupConfiguration(const QString& id, const QString& name); + + QString id() const override; + void setId(const QString& id) override; + QString displayName() const override; + void setDisplayName(const QString& displayName) override; + QString projectServiceId() const override; + + QUrl baseUrl() const; + void setBaseUrl(const QUrl& baseUrl); + +private: + QString m_id; + QString m_name; + QUrl m_baseUrl; +}; + +#endif // GITLABHOOKUPCONFIGURATION_H diff --git a/plugins/gitlab/gitlabhookupconfiguration.cpp b/plugins/gitlab/gitlabhookupconfiguration.cpp new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabhookupconfiguration.cpp @@ -0,0 +1,62 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "gitlabhookupconfiguration.h" + +GitLabHookupConfiguration::GitLabHookupConfiguration(const QString& id, const QString& name) + : m_id(id) + , m_name(name) +{ +} + +QString GitLabHookupConfiguration::id() const +{ + return m_id; +} + +void GitLabHookupConfiguration::setId(const QString& id) +{ + m_id = id; +} + +QString GitLabHookupConfiguration::displayName() const +{ + return m_name; +} + +void GitLabHookupConfiguration::setDisplayName(const QString& displayName) +{ + m_name = displayName; +} + +QString GitLabHookupConfiguration::projectServiceId() const +{ + return QStringLiteral("gitlab"); +} + +void GitLabHookupConfiguration::setBaseUrl(const QUrl& baseUrl) +{ + m_baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash); +} + + +QUrl GitLabHookupConfiguration::baseUrl() const +{ + return m_baseUrl; +} diff --git a/plugins/gitlab/gitlabplugin.h b/plugins/gitlab/gitlabplugin.h new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabplugin.h @@ -0,0 +1,70 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 GITLABPLUGIN_H +#define GITLABPLUGIN_H + +#include +#include +#include + +#include "projectvcstracker.h" + +class GitLabPlugin : public KDevelop::IPlugin, + public KDevelop::IProjectServiceHookup, + public KDevelop::IProjectServiceUrlMapper +{ + Q_OBJECT + Q_INTERFACES(KDevelop::IProjectServiceHookup) + Q_INTERFACES(KDevelop::IProjectServiceUrlMapper) + +public: + /** + * Constructor with arguments as needed with KPluginFactory + * + * @param parent + * @param args + */ + GitLabPlugin(QObject* parent, const QVariantList& args); + ~GitLabPlugin() override; + +public: // IPlugin API + QString projectServiceId() const override; + QString projectServiceDisplayName() const override; + QIcon projectServiceIcon() const override; + +public: // IProjectServiceHookup API + QList loadProjectServiceHookupConfigurations(KDevelop::IProject* project) const override; + void saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + KDevelop::IProject* project) override; + KDevelop::IProjectServiceHookupConfiguration* createProjectServiceHookupConfiguration(KDevelop::IProject* project) const override; + + KDevelop::ProjectServiceHookupConfigPage* createProjectServiceHookupConfigPage(QWidget* parent) const override; + +public: // IProjectServiceUrlMapper API + ProjectServiceUrlMapperCapabilities projectServiceUrlMapperCapabilities() const override; + QVector + projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, + KDevelop::IProject* project) const override; + +private: + ProjectVcsTracker m_projectVcsTracker; +}; + +#endif // GITLABPLUGIN_H diff --git a/plugins/gitlab/gitlabplugin.cpp b/plugins/gitlab/gitlabplugin.cpp new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabplugin.cpp @@ -0,0 +1,187 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "gitlabplugin.h" + +#include "gitlabhookupconfiguration.h" +#include "gitlabhookupconfigpage.h" + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace KDevelop; + +K_PLUGIN_FACTORY_WITH_JSON(GitLabPluginFactory, "gitlabplugin.json", + registerPlugin();) + + +namespace { + +namespace Strings { +inline QString gitlabGroupKey() { return QStringLiteral("GitLab Settings"); } +inline QString nameKey() { return QStringLiteral("Name"); } +inline QString baseUrlKey() { return QStringLiteral("BaseUrl"); } +inline QString repositoryNameKey() { return QStringLiteral("RepositoryName"); } +} + +} + + +GitLabPlugin::GitLabPlugin(QObject *parent, const QVariantList& args) + : KDevelop::IPlugin(QStringLiteral("kdevgitlab"), parent) +{ + Q_UNUSED(args); + +} + +GitLabPlugin::~GitLabPlugin() = default; + + +QString GitLabPlugin::projectServiceId() const +{ + return QStringLiteral("gitlab"); +} + +QString GitLabPlugin::projectServiceDisplayName() const +{ + return QStringLiteral("GitLab"); +} + +QIcon GitLabPlugin::projectServiceIcon() const +{ + return QIcon::fromTheme(QStringLiteral("gitlab")); +} + +QList GitLabPlugin::loadProjectServiceHookupConfigurations(IProject* project) const +{ + QList result; + + KConfigGroup cfg(project->projectConfiguration(), Strings::gitlabGroupKey()); + + for (const auto& groupName : cfg.groupList()) { + KConfigGroup configurationGroup(&cfg, groupName); + + const QString name = configurationGroup.readEntry(Strings::nameKey(), QString()); + auto gitlabConfiguration = new GitLabHookupConfiguration(groupName, name); + const QUrl baseUrl = QUrl::fromUserInput(configurationGroup.readEntry(Strings::baseUrlKey(), QString())); + gitlabConfiguration->setBaseUrl(baseUrl); + + result << gitlabConfiguration; + } + + return result; +} + +void GitLabPlugin::saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + IProject* project) +{ + QStringList configurationNames; + for (auto configuration : projectServiceHookupConfigurations) { + configurationNames.append(configuration->id()); + } + + KConfigGroup cfg(project->projectConfiguration(), Strings::gitlabGroupKey()); + + // clean up no longer existing configurations + for (const auto& groupName : cfg.groupList()) { + if (!configurationNames.contains(groupName)) { + cfg.deleteGroup(groupName); + } + } + + // store current ones + for (auto configuration : projectServiceHookupConfigurations) { + auto gitlabConfiguration = dynamic_cast(configuration); + Q_ASSERT(gitlabConfiguration); + if (!gitlabConfiguration) { + continue; + } + KConfigGroup configurationGroup(&cfg, configuration->id()); + configurationGroup.deleteGroup(); + + configurationGroup.writeEntry(Strings::nameKey(), gitlabConfiguration->displayName()); + configurationGroup.writeEntry(Strings::baseUrlKey(), gitlabConfiguration->baseUrl().toString()); + } + + cfg.sync(); +} + +IProjectServiceHookupConfiguration* GitLabPlugin::createProjectServiceHookupConfiguration(IProject* project) const +{ + Q_UNUSED(project); + return new GitLabHookupConfiguration(projectServiceId(), projectServiceDisplayName()); +} + +ProjectServiceHookupConfigPage* GitLabPlugin::createProjectServiceHookupConfigPage(QWidget* parent) const +{ + return new GitLabHookupConfigPage(parent); +} + +IProjectServiceUrlMapper::ProjectServiceUrlMapperCapabilities GitLabPlugin::projectServiceUrlMapperCapabilities() const +{ + return TextSelectionProjectUrlMapping; +} + +QVector GitLabPlugin::projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, IProject* project) const +{ + const QString relativeFilePath = project->path().relativePath(Path(url)); + if (relativeFilePath.isEmpty() || relativeFilePath.startsWith(QLatin1String("../"))) { + return {}; + } + + QVector result; + const auto configurations = loadProjectServiceHookupConfigurations(project); + for (auto configuration : configurations) { + const auto gitlabConfiguration = dynamic_cast(configuration); + + QUrl url = gitlabConfiguration->baseUrl(); + if (!url.isValid()) { + continue; + } + + // TODO: get the remote tracked branch of the matching remote repo here instead + const QString branch = m_projectVcsTracker.currentBranchName(project); + if (branch.isEmpty()) { + continue; + } + + // TODO: find out what url encoding branch and relativeFilePath need + const QString path = url.path() + QLatin1String("/blob/") + branch + QLatin1Char('/') + relativeFilePath; + url.setPath(path); + url.setFragment(QLatin1Char('L') + QString::number(cursor.line()+1)); + + ProjectServiceUrlData data; + data.url = url; + data.name = gitlabConfiguration->displayName(); + data.icon = projectServiceIcon(); + result << data; + } + + return result; +} + +// needed for QObject class created from K_PLUGIN_FACTORY_WITH_JSON +#include "gitlabplugin.moc" diff --git a/plugins/gitlab/gitlabplugin.json b/plugins/gitlab/gitlabplugin.json new file mode 100644 --- /dev/null +++ b/plugins/gitlab/gitlabplugin.json @@ -0,0 +1,18 @@ +{ + "KPlugin": { + "Id": "kdevgitlab", + "Name": "GitLab Support", + "Description": "Enables integration of GitLab web services", + "Icon": "gitlab", + "Category": "Utilities", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Interfaces": [ + "org.kdevelop.IProjectServiceHookup", + "org.kdevelop.IProjectServiceUrlMapper" + ], + "X-KDevelop-Mode": "GUI" +} diff --git a/plugins/gitlab/icons/CMakeLists.txt b/plugins/gitlab/icons/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/gitlab/icons/CMakeLists.txt @@ -0,0 +1,4 @@ +# icon from https://about.gitlab.com/press/ +ecm_install_icons(ICONS sc-apps-gitlab.svg + DESTINATION ${KDE_INSTALL_ICONDIR} + THEME hicolor) diff --git a/plugins/gitlab/icons/sc-apps-gitlab.svg b/plugins/gitlab/icons/sc-apps-gitlab.svg new file mode 100644 --- /dev/null +++ b/plugins/gitlab/icons/sc-apps-gitlab.svg @@ -0,0 +1,53 @@ + + + + logo-square + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/gitlab/projectvcstracker.h b/plugins/gitlab/projectvcstracker.h new file mode 100644 --- /dev/null +++ b/plugins/gitlab/projectvcstracker.h @@ -0,0 +1,57 @@ +/* This file is part of KDevelop + Copyright 2013 Aleix Pol + Copyright 2017 Friedrich W. H. Kossebau + + 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 PROJECTVCSTRACKER_H +#define PROJECTVCSTRACKER_H + +#include +#include +#include + +class QUrl; + +namespace KDevelop { + class IProject; + class VcsJob; +} + +// TODO: duplicated from cgit, think about having this somewhere shared +class ProjectVcsTracker : public QObject +{ + Q_OBJECT + +public: + explicit ProjectVcsTracker(QObject* parent = nullptr); + + QString currentBranchName(KDevelop::IProject* project) const; + +private: + void startProjectTracking(KDevelop::IProject* project); + void stopProjectTracking(KDevelop::IProject* project); + void onBranchNameReady(KDevelop::VcsJob* job); +private Q_SLOTS: // needs to be tagged as slot for a string-based connect + void handleRepositoryBranchChange(const QUrl& url); + +private: + QSet m_pendingProjects; + QHash m_branchNamePerProject; +}; + +#endif // PROJECTVCSTRACKER_H diff --git a/plugins/gitlab/projectvcstracker.cpp b/plugins/gitlab/projectvcstracker.cpp new file mode 100644 --- /dev/null +++ b/plugins/gitlab/projectvcstracker.cpp @@ -0,0 +1,139 @@ +/* This file is part of KDevelop + Copyright 2013 Aleix Pol + Copyright 2017 Friedrich W. H. Kossebau + + 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 "projectvcstracker.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace KDevelop; + +using SafeProjectPointer = QPointer; +Q_DECLARE_METATYPE(SafeProjectPointer) + +ProjectVcsTracker::ProjectVcsTracker(QObject* parent) + : QObject(parent) +{ + auto projectController = ICore::self()->projectController(); + + connect(projectController, &IProjectController::projectOpened, + this, &ProjectVcsTracker::startProjectTracking); + connect(projectController, &IProjectController::projectClosing, + this, &ProjectVcsTracker::stopProjectTracking); + + for (const auto project : projectController->projects()) { + startProjectTracking(project); + } +} + +QString ProjectVcsTracker::currentBranchName(IProject* project) const +{ + return m_branchNamePerProject.value(project); +} + +void ProjectVcsTracker::startProjectTracking(IProject* project) +{ + IPlugin* plugin = project->versionControlPlugin(); + if (!plugin) { + return; + } + + // TODO: Show revision in case we're in "detached" state + IBranchingVersionControl* branchingExtension = plugin->extension(); + if (branchingExtension) { + const QUrl url = project->path().toUrl(); + branchingExtension->registerRepositoryForCurrentBranchChanges(url); + //can't use new signal/slot syntax here, IBranchingVersionControl is not a QObject + connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), SLOT(handleRepositoryBranchChange(QUrl))); + handleRepositoryBranchChange(url); + } +} + +void ProjectVcsTracker::handleRepositoryBranchChange(const QUrl& url) +{ + const auto allProjects = ICore::self()->projectController()->projects(); + for (IProject* project : allProjects) { + const QUrl projectUrl = project->path().toUrl(); + const bool isExactMatch = url.matches(projectUrl, QUrl::StripTrailingSlash); + const bool isParentOf = url.isParentOf(projectUrl); + if (isParentOf || isExactMatch) { + // example projects in KDevelop: + // - /path/to/mygitrepo/: isParentOf=0 isExactMatch=1, + // - /path/to/mygitrepo/myproject: isParentOf=1 isExactMatch=0 + // - /path/to/norepo: isParentOf=0 isExactMatch=0 + // isParentOf=1 isExactMatch=1 is not a valid combination + + IPlugin* v = project->versionControlPlugin(); + Q_ASSERT(!isExactMatch || v); // project url == 'change' url => project should be associated with a VCS plugin + if (!v) { + continue; + } + + IBranchingVersionControl* branching = v->extension(); + Q_ASSERT(branching); + VcsJob* job = branching->currentBranch(url); + connect(job, &VcsJob::resultsReady, this, &ProjectVcsTracker::onBranchNameReady); + // pass the current project instance as QPointer, to track if this very instance + // is still alive when the job is done. this protects against the rare chance + // that another project instance reuses the same address and wrongly would be + // matched by just comparing pointers + job->setProperty("project", QVariant::fromValue(project)); + ICore::self()->runController()->registerJob(job); + if (!m_branchNamePerProject.contains(project) && !m_pendingProjects.contains(project)) { + m_pendingProjects.insert(project); + } + } + } +} + +void ProjectVcsTracker::onBranchNameReady(VcsJob* job) +{ + if (job->status() == VcsJob::JobSucceeded) { + SafeProjectPointer p = job->property("project").value(); + IProject* project = p.data(); + if (project) { + const auto branchName = job->fetchResults().toString(); + + if (m_pendingProjects.contains(project)) { + m_pendingProjects.remove(project); + m_branchNamePerProject.insert(project, branchName); + } else { + auto it = m_branchNamePerProject.find(project); + if (it != m_branchNamePerProject.end()) { + it.value() = branchName; + } + } + } + } +} + +void ProjectVcsTracker::stopProjectTracking(IProject* project) +{ + m_pendingProjects.remove(project); + m_branchNamePerProject.remove(project); +} diff --git a/plugins/phabricator/CMakeLists.txt b/plugins/phabricator/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/phabricator/CMakeLists.txt @@ -0,0 +1,24 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevphabricator\") + +add_subdirectory(icons) + +set(kdevphabricator_SRCS + projectvcstracker.cpp + phabricatorplugin.cpp + phabricatorhookupconfiguration.cpp + phabricatorhookupconfigpage.cpp +) + +ki18n_wrap_ui(kdevphabricator_SRCS + phabricatorhookupconfigpage.ui +) + +kdevplatform_add_plugin(kdevphabricator + JSON phabricatorplugin.json + SOURCES ${kdevphabricator_SRCS} +) + +target_link_libraries(kdevphabricator + KDev::Vcs + KDev::Services +) diff --git a/plugins/phabricator/Messages.sh b/plugins/phabricator/Messages.sh new file mode 100644 --- /dev/null +++ b/plugins/phabricator/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevphabricator.pot +rm -f rc.cpp diff --git a/plugins/phabricator/icons/CMakeLists.txt b/plugins/phabricator/icons/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/phabricator/icons/CMakeLists.txt @@ -0,0 +1,4 @@ +# icon from https://de.wikipedia.org/wiki/Datei:Phacility_phabricator_logo.svg, Apache Licence Version 2.0 +ecm_install_icons(ICONS sc-apps-phabricator.svg + DESTINATION ${KDE_INSTALL_ICONDIR} + THEME hicolor) diff --git a/plugins/phabricator/icons/sc-apps-phabricator.svg b/plugins/phabricator/icons/sc-apps-phabricator.svg new file mode 100644 --- /dev/null +++ b/plugins/phabricator/icons/sc-apps-phabricator.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/phabricator/phabricatorhookupconfigpage.h b/plugins/phabricator/phabricatorhookupconfigpage.h new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorhookupconfigpage.h @@ -0,0 +1,45 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 PHABRICATORHOOKUPCONFIGPAGE_H +#define PHABRICATORHOOKUPCONFIGPAGE_H + +#include + +namespace Ui +{ +class PhabricatorHookupConfigPage; +} + +class PhabricatorHookupConfigPage : public KDevelop::ProjectServiceHookupConfigPage +{ + Q_OBJECT + +public: + explicit PhabricatorHookupConfigPage(QWidget* parent); + ~PhabricatorHookupConfigPage() override; + + void loadFromConfiguration(const KDevelop::IProjectServiceHookupConfiguration* configuration) override; + void saveToConfiguration(KDevelop::IProjectServiceHookupConfiguration* configuration) const override; + +private: + QScopedPointer m_ui; +}; + +#endif // PHABRICATORHOOKUPCONFIGPAGE_H diff --git a/plugins/phabricator/phabricatorhookupconfigpage.cpp b/plugins/phabricator/phabricatorhookupconfigpage.cpp new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorhookupconfigpage.cpp @@ -0,0 +1,54 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "phabricatorhookupconfigpage.h" +#include "ui_phabricatorhookupconfigpage.h" + +#include "phabricatorhookupconfiguration.h" + +using namespace KDevelop; + +PhabricatorHookupConfigPage::PhabricatorHookupConfigPage(QWidget* parent) + : ProjectServiceHookupConfigPage(parent) + , m_ui(new Ui::PhabricatorHookupConfigPage) +{ + m_ui->setupUi(this); + connect(m_ui->baseUrlEdit, &QLineEdit::textEdited, this, &PhabricatorHookupConfigPage::changed); + connect(m_ui->repositoryNameEdit, &QLineEdit::textEdited, this, &PhabricatorHookupConfigPage::changed); + connect(m_ui->repositoryIdEdit, &QLineEdit::textEdited, this, &PhabricatorHookupConfigPage::changed); +} + +PhabricatorHookupConfigPage::~PhabricatorHookupConfigPage() = default; + + +void PhabricatorHookupConfigPage::loadFromConfiguration(const IProjectServiceHookupConfiguration* configuration) +{ + const PhabricatorHookupConfiguration* phabricatorHookupConfiguration = dynamic_cast(configuration); + m_ui->baseUrlEdit->setText(phabricatorHookupConfiguration->baseUrl().toString()); + m_ui->repositoryNameEdit->setText(phabricatorHookupConfiguration->repositoryName()); + m_ui->repositoryIdEdit->setText(phabricatorHookupConfiguration->repositoryId()); +} + +void PhabricatorHookupConfigPage::saveToConfiguration(IProjectServiceHookupConfiguration* configuration) const +{ + PhabricatorHookupConfiguration* phabricatorHookupConfiguration = dynamic_cast(configuration); + phabricatorHookupConfiguration->setBaseUrl(QUrl::fromUserInput(m_ui->baseUrlEdit->text())); + phabricatorHookupConfiguration->setRepositoryName(m_ui->repositoryNameEdit->text()); + phabricatorHookupConfiguration->setRepositoryId(m_ui->repositoryIdEdit->text()); +} diff --git a/plugins/phabricator/phabricatorhookupconfigpage.ui b/plugins/phabricator/phabricatorhookupconfigpage.ui new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorhookupconfigpage.ui @@ -0,0 +1,72 @@ + + + PhabricatorHookupConfigPage + + + + 0 + 0 + 198 + 29 + + + + + 0 + + + + + Base URL: + + + + + + + URL of the Phabricator main page (https://host/path) + + + true + + + + + + + Repository name: + + + + + + + Name of the repository as used in the URL + + + true + + + + + + + Repository id: + + + + + + + Id of the repository as used in the URL (Rxyz) + + + true + + + + + + + + diff --git a/plugins/phabricator/phabricatorhookupconfiguration.h b/plugins/phabricator/phabricatorhookupconfiguration.h new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorhookupconfiguration.h @@ -0,0 +1,54 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 PHABRICATORHOOKUPCONFIGURATION_H +#define PHABRICATORHOOKUPCONFIGURATION_H + +#include + +#include +#include + +class PhabricatorHookupConfiguration : public KDevelop::IProjectServiceHookupConfiguration +{ +public: + PhabricatorHookupConfiguration(const QString& id, const QString& name); + + QString id() const override; + void setId(const QString& id) override; + QString displayName() const override; + void setDisplayName(const QString& displayName) override; + QString projectServiceId() const override; + + QUrl baseUrl() const; + QString repositoryName() const; + QString repositoryId() const; + void setBaseUrl(const QUrl& baseUrl); + void setRepositoryName(const QString& repositoryName); + void setRepositoryId(const QString& repositoryId); + +private: + QString m_id; + QString m_name; + QUrl m_baseUrl; + QString m_repositoryName; + QString m_repositoryId; +}; + +#endif // PHABRICATORHOOKUPCONFIGURATION_H diff --git a/plugins/phabricator/phabricatorhookupconfiguration.cpp b/plugins/phabricator/phabricatorhookupconfiguration.cpp new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorhookupconfiguration.cpp @@ -0,0 +1,82 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "phabricatorhookupconfiguration.h" + +PhabricatorHookupConfiguration::PhabricatorHookupConfiguration(const QString& id, const QString& name) + : m_id(id) + , m_name(name) +{ +} + +QString PhabricatorHookupConfiguration::id() const +{ + return m_id; +} + +void PhabricatorHookupConfiguration::setId(const QString& id) +{ + m_id = id; +} + +QString PhabricatorHookupConfiguration::displayName() const +{ + return m_name; +} + +void PhabricatorHookupConfiguration::setDisplayName(const QString& displayName) +{ + m_name = displayName; +} + +QString PhabricatorHookupConfiguration::projectServiceId() const +{ + return QStringLiteral("phabricator"); +} + +void PhabricatorHookupConfiguration::setBaseUrl(const QUrl& baseUrl) +{ + m_baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash); +} + +void PhabricatorHookupConfiguration::setRepositoryName(const QString& repositoryName) +{ + m_repositoryName = repositoryName; +} + +void PhabricatorHookupConfiguration::setRepositoryId(const QString& repositoryId) +{ + m_repositoryId = repositoryId; +} + + +QUrl PhabricatorHookupConfiguration::baseUrl() const +{ + return m_baseUrl; +} + +QString PhabricatorHookupConfiguration::repositoryName() const +{ + return m_repositoryName; +} + +QString PhabricatorHookupConfiguration::repositoryId() const +{ + return m_repositoryId; +} diff --git a/plugins/phabricator/phabricatorplugin.h b/plugins/phabricator/phabricatorplugin.h new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorplugin.h @@ -0,0 +1,73 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 PHABRICATORPLUGIN_H +#define PHABRICATORPLUGIN_H + +#include +#include +#include + +#include "projectvcstracker.h" + +class PhabricatorPlugin : public KDevelop::IPlugin, + public KDevelop::IProjectServiceHookup, + public KDevelop::IProjectServiceUrlMapper +{ + Q_OBJECT + Q_INTERFACES( KDevelop::IProjectServiceHookup ) + Q_INTERFACES( KDevelop::IProjectServiceUrlMapper ) + +public: + /** + * Constructor with arguments as needed with KPluginFactory + * + * @param parent + * @param args + */ + PhabricatorPlugin(QObject* parent, const QVariantList& args); + ~PhabricatorPlugin() override; + +public: // IPlugin API + QString projectServiceId() const override; + QString projectServiceDisplayName() const override; + QIcon projectServiceIcon() const override; + +public: // IProjectServiceHookup API + QList loadProjectServiceHookupConfigurations(KDevelop::IProject* project) const override; + void saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + KDevelop::IProject* project) override; + KDevelop::IProjectServiceHookupConfiguration* createProjectServiceHookupConfiguration(KDevelop::IProject* project) const override; + + KDevelop::ProjectServiceHookupConfigPage* createProjectServiceHookupConfigPage(QWidget* parent) const override; + +public: // IProjectServiceUrlMapper API + ProjectServiceUrlMapperCapabilities projectServiceUrlMapperCapabilities() const override; + QVector + projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, + KDevelop::IProject* project) const override; + QVector + projectServiceUrlsFromVcsRevision(const KDevelop::VcsRevision& revision, KDevelop::IProject* project) const override; + using IProjectServiceUrlMapper::projectServiceUrlsFromVcsRevision; + +private: + ProjectVcsTracker m_projectVcsTracker; +}; + +#endif // PHABRICATORPLUGIN_H diff --git a/plugins/phabricator/phabricatorplugin.cpp b/plugins/phabricator/phabricatorplugin.cpp new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorplugin.cpp @@ -0,0 +1,228 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "phabricatorplugin.h" + +#include "phabricatorhookupconfiguration.h" +#include "phabricatorhookupconfigpage.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace KDevelop; + +K_PLUGIN_FACTORY_WITH_JSON(PhabricatorPluginFactory, "phabricatorplugin.json", + registerPlugin();) + + +namespace { + +namespace Strings { +inline QString phabricatorGroupKey() { return QStringLiteral("Phabricator Settings"); } +inline QString nameKey() { return QStringLiteral("Name"); } +inline QString baseUrlKey() { return QStringLiteral("BaseUrl"); } +inline QString repositoryNameKey() { return QStringLiteral("RepositoryName"); } +inline QString repositoryIdKey() { return QStringLiteral("RepositoryId"); } +} + +} + + +PhabricatorPlugin::PhabricatorPlugin(QObject *parent, const QVariantList& args) + : KDevelop::IPlugin(QStringLiteral("kdevphabricator"), parent) +{ + Q_UNUSED(args); + +} + +PhabricatorPlugin::~PhabricatorPlugin() = default; + + +QString PhabricatorPlugin::projectServiceId() const +{ + return QStringLiteral("phabricator"); +} + +QString PhabricatorPlugin::projectServiceDisplayName() const +{ + return QStringLiteral("Phabricator"); +} + +QIcon PhabricatorPlugin::projectServiceIcon() const +{ + return QIcon::fromTheme(QStringLiteral("phabricator")); +} + +QList PhabricatorPlugin::loadProjectServiceHookupConfigurations(IProject* project) const +{ + QList result; + + KConfigGroup cfg(project->projectConfiguration(), Strings::phabricatorGroupKey()); + + for (const auto& groupName : cfg.groupList()) { + KConfigGroup configurationGroup(&cfg, groupName); + + const QString name = configurationGroup.readEntry(Strings::nameKey(), QString()); + auto phabricatorConfiguration = new PhabricatorHookupConfiguration(groupName, name); + const QUrl baseUrl = QUrl::fromUserInput(configurationGroup.readEntry(Strings::baseUrlKey(), QString())); + phabricatorConfiguration->setBaseUrl(baseUrl); + const QString repositoryName = configurationGroup.readEntry(Strings::repositoryNameKey(), QString()); + phabricatorConfiguration->setRepositoryName(repositoryName); + const QString repositoryId = configurationGroup.readEntry(Strings::repositoryIdKey(), QString()); + phabricatorConfiguration->setRepositoryId(repositoryId); + + result << phabricatorConfiguration; + } + + return result; +} + +void PhabricatorPlugin::saveProjectServiceHookupConfigurations(const QList& projectServiceHookupConfigurations, + IProject* project) +{ + QStringList configurationNames; + for (auto configuration : projectServiceHookupConfigurations) { + configurationNames.append(configuration->id()); + } + + KConfigGroup cfg(project->projectConfiguration(), Strings::phabricatorGroupKey()); + + // clean up no longer existing configurations + for (const auto& groupName : cfg.groupList()) { + if (!configurationNames.contains(groupName)) { + cfg.deleteGroup(groupName); + } + } + + // store current ones + for (auto configuration : projectServiceHookupConfigurations) { + auto phabricatorConfiguration = dynamic_cast(configuration); + Q_ASSERT(phabricatorConfiguration); + if (!phabricatorConfiguration) { + continue; + } + KConfigGroup configurationGroup(&cfg, configuration->id()); + configurationGroup.deleteGroup(); + + configurationGroup.writeEntry(Strings::nameKey(), phabricatorConfiguration->displayName()); + configurationGroup.writeEntry(Strings::baseUrlKey(), phabricatorConfiguration->baseUrl().toString()); + configurationGroup.writeEntry(Strings::repositoryNameKey(), phabricatorConfiguration->repositoryName()); + configurationGroup.writeEntry(Strings::repositoryIdKey(), phabricatorConfiguration->repositoryId()); + } + + cfg.sync(); +} + +IProjectServiceHookupConfiguration* PhabricatorPlugin::createProjectServiceHookupConfiguration(IProject* project) const +{ + Q_UNUSED(project); + return new PhabricatorHookupConfiguration(projectServiceId(), projectServiceDisplayName()); +} + +ProjectServiceHookupConfigPage* PhabricatorPlugin::createProjectServiceHookupConfigPage(QWidget* parent) const +{ + return new PhabricatorHookupConfigPage(parent); +} + +IProjectServiceUrlMapper::ProjectServiceUrlMapperCapabilities PhabricatorPlugin::projectServiceUrlMapperCapabilities() const +{ + return TextSelectionProjectUrlMapping | VcsRevisionProjectUrlMapping; +} + +QVector PhabricatorPlugin::projectServiceUrlsFromTextSelection(const QUrl& url, const KTextEditor::Cursor& cursor, IProject* project) const +{ + const QString relativeFilePath = project->path().relativePath(Path(url)); + if (relativeFilePath.isEmpty() || relativeFilePath.startsWith(QLatin1String("../"))) { + return {}; + } + + QVector result; + const auto configurations = loadProjectServiceHookupConfigurations(project); + for (auto configuration : configurations) { + const auto phabricatorConfiguration = dynamic_cast(configuration); + + const QString repositoryName = phabricatorConfiguration->repositoryName(); + QUrl url = phabricatorConfiguration->baseUrl(); + if (!url.isValid()) { + continue; + } + + // TODO: get the remote tracked branch of the matching remote repo here instead + const QString branch = m_projectVcsTracker.currentBranchName(project); + if (branch.isEmpty()) { + continue; + } + + // TODO: find out what url encoding branch and relativeFilePath need + const QString path = url.path() + QStringLiteral("/source/") + repositoryName + QLatin1String("/browse/") + branch + QLatin1Char('/') + relativeFilePath + QLatin1String("$") + QString::number(cursor.line()+1); + url.setPath(path); + + ProjectServiceUrlData data; + data.url = url; + data.name = phabricatorConfiguration->displayName(); + data.icon = projectServiceIcon(); + result << data; + } + + return result; +} + +QVector +PhabricatorPlugin::projectServiceUrlsFromVcsRevision(const VcsRevision& revision, IProject* project) const +{ + QVector result; + const QString revisionHash = revision.revisionValue().toString(); + if (revisionHash.isEmpty()) { + return result; + } + + const auto configurations = loadProjectServiceHookupConfigurations(project); + for (auto configuration : configurations) { + const auto phabricatorConfiguration = dynamic_cast(configuration); + + const QString repositoryId = phabricatorConfiguration->repositoryId(); + QUrl url = phabricatorConfiguration->baseUrl(); + if (!url.isValid()) { + continue; + } + + // TODO: find out what url encoding branch and relativeFilePath need + const QString path = url.path() + QLatin1Char('/') + repositoryId + QLatin1Char(':') + revisionHash; + url.setPath(path); + + ProjectServiceUrlData data; + data.url = url; + data.name = phabricatorConfiguration->displayName(); + data.icon = projectServiceIcon(); + result << data; + } + + return result; +} + +// needed for QObject class created from K_PLUGIN_FACTORY_WITH_JSON +#include "phabricatorplugin.moc" diff --git a/plugins/phabricator/phabricatorplugin.json b/plugins/phabricator/phabricatorplugin.json new file mode 100644 --- /dev/null +++ b/plugins/phabricator/phabricatorplugin.json @@ -0,0 +1,18 @@ +{ + "KPlugin": { + "Id": "kdevphabricator", + "Name": "Phabricator Support", + "Description": "Enables integration of Phabricator web services", + "Icon": "phabricator", + "Category": "Utilities", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Interfaces": [ + "org.kdevelop.IProjectServiceHookup", + "org.kdevelop.IProjectServiceUrlMapper" + ], + "X-KDevelop-Mode": "GUI" +} diff --git a/plugins/phabricator/projectvcstracker.h b/plugins/phabricator/projectvcstracker.h new file mode 100644 --- /dev/null +++ b/plugins/phabricator/projectvcstracker.h @@ -0,0 +1,57 @@ +/* This file is part of KDevelop + Copyright 2013 Aleix Pol + Copyright 2017 Friedrich W. H. Kossebau + + 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 PROJECTVCSTRACKER_H +#define PROJECTVCSTRACKER_H + +#include +#include +#include + +class QUrl; + +namespace KDevelop { + class IProject; + class VcsJob; +} + +// TODO: duplicated from cgit, think about having this somewhere shared +class ProjectVcsTracker : public QObject +{ + Q_OBJECT + +public: + explicit ProjectVcsTracker(QObject* parent = nullptr); + + QString currentBranchName(KDevelop::IProject* project) const; + +private: + void startProjectTracking(KDevelop::IProject* project); + void stopProjectTracking(KDevelop::IProject* project); + void onBranchNameReady(KDevelop::VcsJob* job); +private Q_SLOTS: // needs to be tagged as slot for a string-based connect + void handleRepositoryBranchChange(const QUrl& url); + +private: + QSet m_pendingProjects; + QHash m_branchNamePerProject; +}; + +#endif // PROJECTVCSTRACKER_H diff --git a/plugins/phabricator/projectvcstracker.cpp b/plugins/phabricator/projectvcstracker.cpp new file mode 100644 --- /dev/null +++ b/plugins/phabricator/projectvcstracker.cpp @@ -0,0 +1,139 @@ +/* This file is part of KDevelop + Copyright 2013 Aleix Pol + Copyright 2017 Friedrich W. H. Kossebau + + 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 "projectvcstracker.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace KDevelop; + +using SafeProjectPointer = QPointer; +Q_DECLARE_METATYPE(SafeProjectPointer) + +ProjectVcsTracker::ProjectVcsTracker(QObject* parent) + : QObject(parent) +{ + auto projectController = ICore::self()->projectController(); + + connect(projectController, &IProjectController::projectOpened, + this, &ProjectVcsTracker::startProjectTracking); + connect(projectController, &IProjectController::projectClosing, + this, &ProjectVcsTracker::stopProjectTracking); + + for (const auto project : projectController->projects()) { + startProjectTracking(project); + } +} + +QString ProjectVcsTracker::currentBranchName(IProject* project) const +{ + return m_branchNamePerProject.value(project); +} + +void ProjectVcsTracker::startProjectTracking(IProject* project) +{ + IPlugin* plugin = project->versionControlPlugin(); + if (!plugin) { + return; + } + + // TODO: Show revision in case we're in "detached" state + IBranchingVersionControl* branchingExtension = plugin->extension(); + if (branchingExtension) { + const QUrl url = project->path().toUrl(); + branchingExtension->registerRepositoryForCurrentBranchChanges(url); + //can't use new signal/slot syntax here, IBranchingVersionControl is not a QObject + connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), SLOT(handleRepositoryBranchChange(QUrl))); + handleRepositoryBranchChange(url); + } +} + +void ProjectVcsTracker::handleRepositoryBranchChange(const QUrl& url) +{ + const auto allProjects = ICore::self()->projectController()->projects(); + for (IProject* project : allProjects) { + const QUrl projectUrl = project->path().toUrl(); + const bool isExactMatch = url.matches(projectUrl, QUrl::StripTrailingSlash); + const bool isParentOf = url.isParentOf(projectUrl); + if (isParentOf || isExactMatch) { + // example projects in KDevelop: + // - /path/to/mygitrepo/: isParentOf=0 isExactMatch=1, + // - /path/to/mygitrepo/myproject: isParentOf=1 isExactMatch=0 + // - /path/to/norepo: isParentOf=0 isExactMatch=0 + // isParentOf=1 isExactMatch=1 is not a valid combination + + IPlugin* v = project->versionControlPlugin(); + Q_ASSERT(!isExactMatch || v); // project url == 'change' url => project should be associated with a VCS plugin + if (!v) { + continue; + } + + IBranchingVersionControl* branching = v->extension(); + Q_ASSERT(branching); + VcsJob* job = branching->currentBranch(url); + connect(job, &VcsJob::resultsReady, this, &ProjectVcsTracker::onBranchNameReady); + // pass the current project instance as QPointer, to track if this very instance + // is still alive when the job is done. this protects against the rare chance + // that another project instance reuses the same address and wrongly would be + // matched by just comparing pointers + job->setProperty("project", QVariant::fromValue(project)); + ICore::self()->runController()->registerJob(job); + if (!m_branchNamePerProject.contains(project) && !m_pendingProjects.contains(project)) { + m_pendingProjects.insert(project); + } + } + } +} + +void ProjectVcsTracker::onBranchNameReady(VcsJob* job) +{ + if (job->status() == VcsJob::JobSucceeded) { + SafeProjectPointer p = job->property("project").value(); + IProject* project = p.data(); + if (project) { + const auto branchName = job->fetchResults().toString(); + + if (m_pendingProjects.contains(project)) { + m_pendingProjects.remove(project); + m_branchNamePerProject.insert(project, branchName); + } else { + auto it = m_branchNamePerProject.find(project); + if (it != m_branchNamePerProject.end()) { + it.value() = branchName; + } + } + } + } +} + +void ProjectVcsTracker::stopProjectTracking(IProject* project) +{ + m_pendingProjects.remove(project); + m_branchNamePerProject.remove(project); +} diff --git a/plugins/projectservicemanager/CMakeLists.txt b/plugins/projectservicemanager/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/CMakeLists.txt @@ -0,0 +1,25 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevprojectservicehookupmanager\") + +set(kdevprojectservicehookupmanager_SRCS + config/projectconfigpage.cpp + config/projectservicehookuplistmodel.cpp + kdevprojectservicehookupmanager.cpp +) + +ki18n_wrap_ui(kdevprojectservicehookupmanager_SRCS + config/projectconfigpage.ui +) + +kconfig_add_kcfg_files(kdevprojectservicehookupmanager_SRCS + config/projectconfig.kcfgc +) + +kdevplatform_add_plugin(kdevprojectservicehookupmanager + JSON kdevprojectservicehookupmanager.json + SOURCES ${kdevprojectservicehookupmanager_SRCS} +) + +target_link_libraries(kdevprojectservicehookupmanager + KDev::Project + KDev::Services +) diff --git a/plugins/projectservicemanager/Messages.sh b/plugins/projectservicemanager/Messages.sh new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevprojectservicehookupmanager.pot +rm -f rc.cpp diff --git a/plugins/projectservicemanager/config/projectconfig.kcfg b/plugins/projectservicemanager/config/projectconfig.kcfg new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectconfig.kcfg @@ -0,0 +1,22 @@ + + + + + diff --git a/plugins/projectservicemanager/config/projectconfig.kcfgc b/plugins/projectservicemanager/config/projectconfig.kcfgc new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectconfig.kcfgc @@ -0,0 +1,3 @@ +File=projectconfig.kcfg +NameSpace=ProjectServiceHookupManager +ClassName=ProjectConfig diff --git a/plugins/projectservicemanager/config/projectconfigpage.h b/plugins/projectservicemanager/config/projectconfigpage.h new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectconfigpage.h @@ -0,0 +1,85 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 KDEVPROJECTSERVICEHOOKUPMANAGER_PROJECTCONFIGPAGE_H +#define KDEVPROJECTSERVICEHOOKUPMANAGER_PROJECTCONFIGPAGE_H + +#include + +#include "projectservicehookuplistmodel.h" + +class QItemSelection; + +namespace KDevelop +{ +class IProject; +class ProjectServiceHookupConfigPage; +} + +namespace ProjectServiceHookupManager { + +namespace Ui +{ +class ProjectConfigPage; +} + +/** + * @todo write docs + */ +class ProjectConfigPage : public KDevelop::ConfigPage +{ + Q_OBJECT + +public: + /** + * Constructor + * + * @param plugin + * @param project + * @param parent + */ + ProjectConfigPage(KDevelop::IPlugin* plugin, KDevelop::IProject* project, QWidget* parent); + ~ProjectConfigPage() override; + + QIcon icon() const override; + QString name() const override; + + void apply() override; + void defaults() override; + void reset() override; + +private: + void addHookupConfiguration(); + void deleteHookupConfiguration(); + void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void onConfigurationsAboutToBeReset(); + void onConfigurationToBeRemoved(KDevelop::IProjectServiceHookupConfiguration* configuration); + void onConfigPageContentChanged(); + +private: + QScopedPointer m_ui; + KDevelop::ProjectServiceHookupConfigPage* m_configurationPage; + KDevelop::IProject* m_project; + ProjectServiceHookupListModel m_serviceHookupListModel; + KDevelop::IProjectServiceHookupConfiguration* m_editedConfiguration; +}; + +} + +#endif // KDEVPROJECTSERVICEHOOKUPMANAGER_PROJECTCONFIGPAGE_H diff --git a/plugins/projectservicemanager/config/projectconfigpage.cpp b/plugins/projectservicemanager/config/projectconfigpage.cpp new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectconfigpage.cpp @@ -0,0 +1,241 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "projectconfigpage.h" +#include "ui_projectconfigpage.h" + +#include "projectconfig.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ProjectServiceHookupManager; +using namespace KDevelop; + +ProjectConfigPage::ProjectConfigPage(KDevelop::IPlugin* plugin, KDevelop::IProject* project, QWidget* parent) + : ConfigPage(plugin, new ProjectConfig, parent) + , m_ui(new Ui::ProjectConfigPage) + , m_configurationPage(nullptr) + , m_project(project) + , m_editedConfiguration(nullptr) +{ + configSkeleton()->setSharedConfig(m_project->projectConfiguration()); + configSkeleton()->load(); + + m_ui->setupUi(this); + + m_ui->removeServiceHookupButton->setEnabled(false); + connect(m_ui->removeServiceHookupButton, &QPushButton::clicked, + this, &ProjectConfigPage::deleteHookupConfiguration); + + QList configurations; + QMenu* serviceHookupsMenu = new QMenu(this); + + const QList plugins = ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IProjectServiceHookup"); + + for (IPlugin* plugin : plugins) { + IProjectServiceHookup* serviceHookup = plugin->extension(); + Q_ASSERT(serviceHookup); + + configurations += serviceHookup->loadProjectServiceHookupConfigurations(m_project); + + QAction* action = new QAction(serviceHookup->projectServiceIcon(), + serviceHookup->projectServiceDisplayName(), serviceHookupsMenu); + action->setData(qVariantFromValue(plugin)); + connect(action, &QAction::triggered, this, &ProjectConfigPage::addHookupConfiguration); + serviceHookupsMenu->addAction(action); + } + + m_serviceHookupListModel.setHookups(plugins); + m_serviceHookupListModel.setConfigurations(configurations); + connect(&m_serviceHookupListModel, &ProjectServiceHookupListModel::configurationAdded, + this, &ProjectConfigPage::changed); + connect(&m_serviceHookupListModel, &ProjectServiceHookupListModel::dataChanged, + this, &ProjectConfigPage::changed); + connect(&m_serviceHookupListModel, &ProjectServiceHookupListModel::modelAboutToBeReset, + this, &ProjectConfigPage::onConfigurationsAboutToBeReset); + connect(&m_serviceHookupListModel, &ProjectServiceHookupListModel::configurationToBeRemoved, + this, &ProjectConfigPage::onConfigurationToBeRemoved); + + m_ui->addServiceHookupButton->setMenu(serviceHookupsMenu); + m_ui->addServiceHookupButton->setEnabled(!serviceHookupsMenu->isEmpty()); + m_ui->configurationsView->setModel(&m_serviceHookupListModel); + connect(m_ui->configurationsView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &ProjectConfigPage::onSelectionChanged); +} + +ProjectConfigPage::~ProjectConfigPage() = default; + +QIcon ProjectConfigPage::icon() const +{ + return QIcon::fromTheme(QStringLiteral("services")); +} + +QString ProjectConfigPage::name() const +{ + return i18n("Service Hook-ups"); +} + +void ProjectConfigPage::apply() +{ + ConfigPage::apply(); + + // + QHash > configurationsByService; + const auto configurations = m_serviceHookupListModel.configurations(); + + for (auto configuration : configurations) { + configurationsByService[configuration->projectServiceId()].append(configuration); + } + + // store for any servicehookup, including those without configuration, + // so they clean up any old stored configs + const auto serviceHookups = m_serviceHookupListModel.hookups(); + + for (auto serviceHookup : serviceHookups) { + const auto configurations = configurationsByService.value(serviceHookup->projectServiceId()); + serviceHookup->saveProjectServiceHookupConfigurations(configurations, m_project); + } +} + +void ProjectConfigPage::defaults() +{ + ConfigPage::defaults(); + + // nothing really maps here to the meaning, removing all hookups might not really be expected +} + +void ProjectConfigPage::reset() +{ + ConfigPage::reset(); + + QList configurations; + + const auto serviceHookups = m_serviceHookupListModel.hookups(); + + for (auto serviceHookup : serviceHookups) { + configurations += serviceHookup->loadProjectServiceHookupConfigurations(m_project); + } + + m_serviceHookupListModel.setConfigurations(configurations); +} + +void ProjectConfigPage::addHookupConfiguration() +{ + QAction* action = qobject_cast(sender()); + Q_ASSERT(action); + + IPlugin* plugin = action->data().value(); + Q_ASSERT(plugin); + IProjectServiceHookup* serviceHookup = plugin->extension(); + Q_ASSERT(serviceHookup); + + IProjectServiceHookupConfiguration* hookupConfiguration = serviceHookup->createProjectServiceHookupConfiguration(m_project); + + const int row = m_serviceHookupListModel.addConfiguration(hookupConfiguration); + + // select in list and start name editing + const QModelIndex index = m_serviceHookupListModel.index(row, ProjectServiceHookupListModel::NameColumn, QModelIndex()); + m_ui->configurationsView->selectionModel()->select(index, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect); + m_ui->configurationsView->edit(index); +} + +void ProjectConfigPage::deleteHookupConfiguration() +{ + const auto selectedRows = m_ui->configurationsView->selectionModel()->selectedRows(); + + if (selectedRows.isEmpty()) { + // should not happen + return; + } + + m_serviceHookupListModel.removeConfiguration(selectedRows.first()); + m_ui->configurationsView->setFocus(Qt::OtherFocusReason); +} + +void ProjectConfigPage::onConfigurationsAboutToBeReset() +{ + if (m_configurationPage) { + m_ui->configPageContainer->removeWidget(m_configurationPage); + delete m_configurationPage; + m_configurationPage = nullptr; + } + m_editedConfiguration = nullptr; +} + +void ProjectConfigPage::onConfigurationToBeRemoved(IProjectServiceHookupConfiguration* configuration) +{ + if (m_editedConfiguration != configuration) { + return; + } + + if (m_configurationPage) { + m_ui->configPageContainer->removeWidget(m_configurationPage); + delete m_configurationPage; + m_configurationPage = nullptr; + } + m_editedConfiguration = nullptr; +} + +void ProjectConfigPage::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + Q_UNUSED(deselected); + + const auto selectedIndexes = selected.indexes(); + const bool hasSelected = !selectedIndexes.isEmpty(); + + m_ui->removeServiceHookupButton->setEnabled(hasSelected); + + if (m_configurationPage) { + m_ui->configPageContainer->removeWidget(m_configurationPage); + delete m_configurationPage; + m_configurationPage = nullptr; + } + + if (hasSelected) { + m_editedConfiguration = m_serviceHookupListModel.configuration(selectedIndexes.first()); + const auto serviceHookup = m_serviceHookupListModel.hookup(m_editedConfiguration->projectServiceId()); + m_configurationPage = serviceHookup->createProjectServiceHookupConfigPage(m_ui->configPageContainer); + if (m_configurationPage) { + m_configurationPage->loadFromConfiguration(m_editedConfiguration); + connect(m_configurationPage, &ProjectServiceHookupConfigPage::changed, + this, &ProjectConfigPage::onConfigPageContentChanged); + const int widgetIndex = m_ui->configPageContainer->addWidget(m_configurationPage); + m_ui->configPageContainer->setCurrentIndex(widgetIndex); + } + } +} + +void ProjectConfigPage::onConfigPageContentChanged() +{ + if (!m_configurationPage || !m_editedConfiguration) { + return; + } + // TODO: improve by squashing updates with some delay timer + m_configurationPage->saveToConfiguration(m_editedConfiguration); + emit changed(); +} diff --git a/plugins/projectservicemanager/config/projectconfigpage.ui b/plugins/projectservicemanager/config/projectconfigpage.ui new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectconfigpage.ui @@ -0,0 +1,71 @@ + + + ProjectServiceHookupManager::ProjectConfigPage + + + + 0 + 0 + 360 + 275 + + + + + + + + + false + + + true + + + true + + + + + + + + + + + + + + Add + + + + .. + + + + + + + Remove + + + + .. + + + + + + + Qt::Vertical + + + + + + + + + + diff --git a/plugins/projectservicemanager/config/projectservicehookuplistmodel.h b/plugins/projectservicemanager/config/projectservicehookuplistmodel.h new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectservicehookuplistmodel.h @@ -0,0 +1,85 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 PROJECTSERVICEHOOKUPLISTMODEL_H +#define PROJECTSERVICEHOOKUPLISTMODEL_H + +#include + +#include +#include + +namespace KDevelop { +class IProjectServiceHookupConfiguration; +class IPlugin; +class IProjectServiceHookup; +} + +namespace ProjectServiceHookupManager +{ + +class ProjectServiceHookupListModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum ColumnIds { + TypeColumn = 0, + NameColumn = 1 + }; + + explicit ProjectServiceHookupListModel(QObject* parent = nullptr); + ~ProjectServiceHookupListModel() override; + + QVariant data(const QModelIndex& index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role) override; + + /** + * @return row of added configuration or -1, if there is no configuration + */ + int addConfiguration(KDevelop::IProjectServiceHookupConfiguration* configuration); + void removeConfiguration(const QModelIndex& index); + void setConfigurations(const QList& configurations); + void setHookups(const QList& plugins); + + QList configurations() const; + KDevelop::IProjectServiceHookupConfiguration* configuration(const QModelIndex& index) const; + + QHash hookups() const; + KDevelop::IProjectServiceHookup* hookup(const QString& projectServiceId) const; + +Q_SIGNALS: + void configurationAdded(KDevelop::IProjectServiceHookupConfiguration* configuration); + void configurationToBeRemoved(KDevelop::IProjectServiceHookupConfiguration* configuration); + void configurationRemoved(KDevelop::IProjectServiceHookupConfiguration* configuration); + +private: + QList m_configurations; + QHash m_hookups; +}; + +} + +#endif // PROJECTSERVICEHOOKUPLISTMODEL_H diff --git a/plugins/projectservicemanager/config/projectservicehookuplistmodel.cpp b/plugins/projectservicemanager/config/projectservicehookuplistmodel.cpp new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/config/projectservicehookuplistmodel.cpp @@ -0,0 +1,259 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "projectservicehookuplistmodel.h" + +#include +#include +#include + +#include + +#include +#include + +using namespace ProjectServiceHookupManager; +using namespace KDevelop; + +ProjectServiceHookupListModel::ProjectServiceHookupListModel(QObject* parent) + : QAbstractItemModel(parent) +{ +} + +ProjectServiceHookupListModel::~ProjectServiceHookupListModel() +{ + qDeleteAll(m_configurations); +} + +QModelIndex ProjectServiceHookupListModel::parent(const QModelIndex& child) const +{ + Q_UNUSED(child); + return {}; +} + +QModelIndex ProjectServiceHookupListModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) { + return {}; + } + + return createIndex(row, column); +} + + +int ProjectServiceHookupListModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + return 2; +} + +int ProjectServiceHookupListModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return m_configurations.size(); + } + + return 0; +} + + +QVariant ProjectServiceHookupListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(orientation); + + if (section < TypeColumn || section > NameColumn) { + return {}; + } + + switch (role) { + case Qt::DisplayRole: + if (section == TypeColumn) { + return i18n("Service Type"); + } + return i18n("Hook-up Name"); + break; + default: + break; + } + + return {}; +} + + +QVariant ProjectServiceHookupListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || + (index.column() < TypeColumn || index.column() > NameColumn) || + (index.row() < 0 || index.row() >= m_configurations.size())) { + return {}; + } + + switch (role) { + case Qt::DisplayRole: { + const IProjectServiceHookupConfiguration* configuration = m_configurations.at(index.row()); + if (index.column() == 0) { + return m_hookups[configuration->projectServiceId()]->projectServiceDisplayName(); + } + if (index.column() == NameColumn) { + return configuration->displayName(); + } + break; + } + case Qt::DecorationRole: { + const IProjectServiceHookupConfiguration* configuration = m_configurations.at(index.row()); + if (index.column() == 0) { + return m_hookups[configuration->projectServiceId()]->projectServiceIcon(); + } + break; + } + case Qt::EditRole: { + const IProjectServiceHookupConfiguration* configuration = m_configurations.at(index.row()); + if (index.column() == NameColumn) { + return configuration->displayName(); + } + break; + } + default: + break; + } + + return {}; +} + +Qt::ItemFlags ProjectServiceHookupListModel::flags(const QModelIndex& index) const +{ + if (index.isValid()) { + if (index.column() == NameColumn) { + return Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + return Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } + return Qt::NoItemFlags; +} + +bool ProjectServiceHookupListModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || + index.column() != NameColumn || + (index.row() < 0 || index.row() >= m_configurations.size()) || + role != Qt::EditRole) { + return false; + } + + const auto configuration = m_configurations.at(index.row()); + const auto newDisplayName = value.toString(); + if (configuration->displayName() != newDisplayName) { + configuration->setDisplayName(newDisplayName); + emit dataChanged(index, index); + return true; + } + + return false; +} + +int ProjectServiceHookupListModel::addConfiguration(IProjectServiceHookupConfiguration* configuration) +{ + Q_ASSERT(configuration); + if (!configuration) { + return -1; + } + + // ensure id is unique + QSet ids; + for (const auto c : m_configurations) { + ids.insert(c->id()); + } + const QString initialId = configuration->id(); + if (ids.contains(initialId)) { + QString newId; + int i = 1; + do { + newId = initialId + QString::number(i++); + } while (ids.contains(newId)); + configuration->setId(newId); + } + + beginInsertRows(QModelIndex(), m_configurations.size(), m_configurations.size()); + // TODO: ensure sorting for consistent display + m_configurations.append(configuration); + endInsertRows(); + emit configurationAdded(configuration); + + return m_configurations.size() - 1; +} + +void ProjectServiceHookupListModel::removeConfiguration(const QModelIndex& index) +{ + const int row = index.row(); + Q_ASSERT(row >= 0 && row < m_configurations.size()); + if (row < 0 || row >= m_configurations.size()) { + return; + } + + beginRemoveRows(QModelIndex(), row, row); + auto configuration = m_configurations.at(row); + emit configurationToBeRemoved(configuration); + m_configurations.removeAt(row); + delete configuration; + endRemoveRows(); + emit configurationRemoved(configuration); +} + + +void ProjectServiceHookupListModel::setConfigurations(const QList& configurations) +{ + beginResetModel(); + qDeleteAll(m_configurations); + // TODO: ensure sorting for consistent display + m_configurations = configurations; + endResetModel(); +} + +void ProjectServiceHookupListModel::setHookups(const QList& plugins) +{ + beginResetModel(); + m_hookups.clear(); + for (auto plugin : plugins) { + IProjectServiceHookup* serviceHookup = plugin->extension(); + m_hookups.insert(serviceHookup->projectServiceId(), serviceHookup); + } + endResetModel(); +} + +QList ProjectServiceHookupListModel::configurations() const +{ + return m_configurations; +} +IProjectServiceHookupConfiguration* ProjectServiceHookupListModel::configuration(const QModelIndex& index) const +{ + const int row = index.row(); + Q_ASSERT(row >= 0 && row < m_configurations.size()); + return m_configurations.at(row); +} + +QHash ProjectServiceHookupListModel::hookups() const +{ + return m_hookups; +} + +IProjectServiceHookup* ProjectServiceHookupListModel::hookup(const QString& projectServiceId) const +{ + Q_ASSERT(m_hookups.contains(projectServiceId)); + return m_hookups.value(projectServiceId); +} diff --git a/plugins/projectservicemanager/kdevprojectservicehookupmanager.h b/plugins/projectservicemanager/kdevprojectservicehookupmanager.h new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/kdevprojectservicehookupmanager.h @@ -0,0 +1,45 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 KDEVPROJECTSERVICEHOOKUPMANAGER_H +#define KDEVPROJECTSERVICEHOOKUPMANAGER_H + +#include + +class KDevProjectServiceHookupManager : public KDevelop::IPlugin +{ + Q_OBJECT + +public: + /** + * Constructor with arguments as needed with KPluginFactory + * + * @param parent + * @param args + */ + KDevProjectServiceHookupManager(QObject* parent, const QVariantList& args); + ~KDevProjectServiceHookupManager() override; + + int perProjectConfigPages() const override; + KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; + +private: +}; + +#endif // KDEVPROJECTSERVICEHOOKUPMANAGER_H diff --git a/plugins/projectservicemanager/kdevprojectservicehookupmanager.cpp b/plugins/projectservicemanager/kdevprojectservicehookupmanager.cpp new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/kdevprojectservicehookupmanager.cpp @@ -0,0 +1,58 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "kdevprojectservicehookupmanager.h" + +#include "config/projectconfigpage.h" + +#include +#include + +#include + +using namespace KDevelop; + +K_PLUGIN_FACTORY_WITH_JSON(KDevProjectServiceHookupManagerFactory, "kdevprojectservicehookupmanager.json", + registerPlugin();) + +KDevProjectServiceHookupManager::KDevProjectServiceHookupManager(QObject *parent, const QVariantList& args) + : KDevelop::IPlugin(QStringLiteral("kdevprojectservicehookupmanager"), parent) +{ + Q_UNUSED(args); +} + +KDevProjectServiceHookupManager::~KDevProjectServiceHookupManager() = default; + +int KDevProjectServiceHookupManager::perProjectConfigPages() const +{ + return 1; +} + +KDevelop::ConfigPage* KDevProjectServiceHookupManager::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, + QWidget* parent) +{ + if (number != 0) { + return nullptr; + } + + return new ProjectServiceHookupManager::ProjectConfigPage(this, options.project, parent); +} + +// needed for QObject class created from K_PLUGIN_FACTORY_WITH_JSON +#include "kdevprojectservicehookupmanager.moc" diff --git a/plugins/projectservicemanager/kdevprojectservicehookupmanager.json b/plugins/projectservicemanager/kdevprojectservicehookupmanager.json new file mode 100644 --- /dev/null +++ b/plugins/projectservicemanager/kdevprojectservicehookupmanager.json @@ -0,0 +1,14 @@ +{ + "KPlugin": { + "Id": "kdevprojectservicehookupmanager", + "Name": "Service hook-ups", + "Description": "Manages hook-up of projects with related services of all kinds", + "Icon": "services", + "Category": "Utilities", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Mode": "GUI" +} diff --git a/plugins/projectserviceurlmapper/CMakeLists.txt b/plugins/projectserviceurlmapper/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/projectserviceurlmapper/CMakeLists.txt @@ -0,0 +1,17 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevprojectserviceurlmapper\") + +set(kdevprojectserviceurlmapper_SRCS + kdevprojectserviceurlmapper.cpp +) + +kdevplatform_add_plugin(kdevprojectserviceurlmapper + JSON kdevprojectserviceurlmapper.json + SOURCES ${kdevprojectserviceurlmapper_SRCS} +) + +target_link_libraries(kdevprojectserviceurlmapper + KDev::Vcs + KDev::Language + KDev::Project + KDev::Services +) diff --git a/plugins/projectserviceurlmapper/Messages.sh b/plugins/projectserviceurlmapper/Messages.sh new file mode 100644 --- /dev/null +++ b/plugins/projectserviceurlmapper/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >> rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevprojectserviceurlmapper.pot +rm -f rc.cpp diff --git a/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.h b/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.h new file mode 100644 --- /dev/null +++ b/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.h @@ -0,0 +1,51 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 KDEVPROJECTSERVICEURLMAPPER_H +#define KDEVPROJECTSERVICEURLMAPPER_H + +#include + +namespace KDevelop { +class ProjectServiceUrlData; +} + +class KDevProjectServiceUrlMapper : public KDevelop::IPlugin +{ + Q_OBJECT + +public: + /** + * Constructor with arguments as needed with KPluginFactory + * + * @param parent + * @param args + */ + KDevProjectServiceUrlMapper(QObject* parent, const QVariantList& args); + ~KDevProjectServiceUrlMapper() override; + + KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; + +private: + QAction* createMenu(const KDevelop::ProjectServiceUrlData& urlData, QWidget* parent); + void openLink(); + void copyLink(); +}; + +#endif // KDEVPROJECTSERVICEURLMAPPER_H diff --git a/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.cpp b/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.cpp new file mode 100644 --- /dev/null +++ b/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.cpp @@ -0,0 +1,235 @@ +/* This file is part of KDevelop + Copyright 2017 Friedrich W. H. Kossebau + + 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 "kdevprojectserviceurlmapper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; + +K_PLUGIN_FACTORY_WITH_JSON(KDevProjectServiceUrlMapperFactory, "kdevprojectserviceurlmapper.json", + registerPlugin();) + +KDevProjectServiceUrlMapper::KDevProjectServiceUrlMapper(QObject *parent, const QVariantList& args) + : KDevelop::IPlugin(QStringLiteral("kdevprojectserviceurlmapper"), parent) +{ + Q_UNUSED(args); +} + +KDevProjectServiceUrlMapper::~KDevProjectServiceUrlMapper() = default; + +QAction* KDevProjectServiceUrlMapper::createMenu(const ProjectServiceUrlData& urlData, QWidget* parent) +{ + auto menuAction = new QAction(urlData.icon, urlData.name, parent); + QMenu* menu = new QMenu(parent); + menuAction->setMenu(menu); + + auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Link")); + action->setData(urlData.url); + connect(action, &QAction::triggered, this, &KDevProjectServiceUrlMapper::openLink); + + action = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link")); + action->setData(urlData.url); + connect(action, &QAction::triggered, this, &KDevProjectServiceUrlMapper::copyLink); + + return menuAction; +} + +KDevelop::ContextMenuExtension KDevProjectServiceUrlMapper::contextMenuExtension(KDevelop::Context* context, + QWidget* parent) +{ + ContextMenuExtension extension; + + if (context->hasType(Context::EditorContext)) { + auto editorContext = dynamic_cast(context); + if (!editorContext) { + return extension; + } + + const QList plugins = ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IProjectServiceUrlMapper"); + if (plugins.isEmpty()) { + return extension; + } + + const QUrl fileUrl = editorContext->url(); + IProject* project = ICore::self()->projectController()->findProjectForUrl(fileUrl); + if (!project) { + return extension; + } + + const QString relativeFilePath = project->path().relativePath(Path(fileUrl)); + if (relativeFilePath.isEmpty() || relativeFilePath.startsWith(QLatin1String("../"))) { + return extension; + } + + // TODO: extend EditorContext to also deliver any current selection & extend IProjectServiceUrlMapper + // to also support selection urls, for those services like phabricator which support it + const KTextEditor::Cursor position = editorContext->position(); + + QList actions; + + for (IPlugin* plugin : plugins) { + IProjectServiceUrlMapper* mapper = plugin->extension(); + Q_ASSERT(mapper); + const QVector projectServiceUrls = mapper->projectServiceUrlsFromTextSelection(fileUrl, position, project); + + for (const auto& projectServiceUrlData : projectServiceUrls) { + actions << createMenu(projectServiceUrlData, parent); + } + } + + if (!actions.isEmpty()) { + auto menuAction = new QAction(QIcon::fromTheme(QStringLiteral("link")), i18n("Document in Services"), parent); + QMenu* menu = new QMenu(parent); + menuAction->setMenu(menu); + for (QAction* action : actions) { + menu->addAction(action); + } + extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); + } + } else if (context->hasType(Context::VcsRevisionContext)) { + auto vcsRevisionContext = dynamic_cast(context); + if (!vcsRevisionContext) { + return extension; + } + + const QList plugins = ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IProjectServiceUrlMapper"); + if (plugins.isEmpty()) { + return extension; + } + + IProject* project = vcsRevisionContext->project(); + if (!project) { + return extension; + } + const auto revision = vcsRevisionContext->revision(); + + + const auto urls = vcsRevisionContext->urls(); + if (urls.count() == 1) { + const auto& url = urls.at(0); + QList perFileActions; + + for (IPlugin* plugin : plugins) { + IProjectServiceUrlMapper* mapper = plugin->extension(); + Q_ASSERT(mapper); + const QVector fileServiceUrls = mapper->projectServiceUrlsFromVcsRevision(url, revision, project); + + for (const auto& fileServiceUrlData : fileServiceUrls) { + perFileActions << createMenu(fileServiceUrlData, parent); + } + } + + if (!perFileActions.isEmpty()) { + auto menuAction = new QAction(QIcon::fromTheme(QStringLiteral("link")), i18n("Revision in Services"), parent); + QMenu* menu = new QMenu(parent); + menuAction->setMenu(menu); + for (QAction* action : perFileActions) { + menu->addAction(action); + } + extension.addAction(ContextMenuExtension::VcsGroup, menuAction); + } + } + + QList perProjectactions; + + for (IPlugin* plugin : plugins) { + IProjectServiceUrlMapper* mapper = plugin->extension(); + Q_ASSERT(mapper); + const QVector projectServiceUrls = mapper->projectServiceUrlsFromVcsRevision(revision, project); + + for (const auto& projectServiceUrlData : projectServiceUrls) { + perProjectactions << createMenu(projectServiceUrlData, parent); + } + } + + if (!perProjectactions.isEmpty()) { + auto menuAction = new QAction(QIcon::fromTheme(QStringLiteral("link")), i18n("Revision in Services (All Files)"), parent); + QMenu* menu = new QMenu(parent); + menuAction->setMenu(menu); + for (QAction* action : perProjectactions) { + menu->addAction(action); + } + extension.addAction(ContextMenuExtension::VcsGroup, menuAction); + } + } + + return extension; +} + + +void KDevProjectServiceUrlMapper::copyLink() +{ + auto action = qobject_cast(sender()); + if (!action) { + return; + } + + const auto url = action->data().toUrl(); + if (!url.isValid()) { + return; + } + + QMimeData* data = new QMimeData; + data->setUrls(QList{url}); // implicitely also sets text + QClipboard *clipboard = QGuiApplication::clipboard(); + // Make sure the url is pastable both with Ctrl-C and with MMB + clipboard->setMimeData(data, QClipboard::Clipboard); + clipboard->setMimeData(data, QClipboard::Selection); +} + + +void KDevProjectServiceUrlMapper::openLink() +{ + auto action = qobject_cast(sender()); + if (!action) { + return; + } + + const auto url = action->data().toUrl(); + if (!url.isValid()) { + return; + } + + QDesktopServices::openUrl(url); +} + + +// needed for QObject class created from K_PLUGIN_FACTORY_WITH_JSON +#include "kdevprojectserviceurlmapper.moc" diff --git a/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.json b/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.json new file mode 100644 --- /dev/null +++ b/plugins/projectserviceurlmapper/kdevprojectserviceurlmapper.json @@ -0,0 +1,14 @@ +{ + "KPlugin": { + "Id": "kdevprojectserviceurlmapper", + "Name": "Service Location Mapper", + "Description": "Maps locations in the project to and from related locations in services", + "Icon": "internet-web-browser", + "Category": "Utilities", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-Mode": "GUI" +}