diff --git a/Architecture.dox b/Architecture.dox
index dc69b49654..1362988a91 100644
--- a/Architecture.dox
+++ b/Architecture.dox
@@ -1,407 +1,402 @@
/** \file Architecture.dox
* \brief KDevelop architecture
*/
/** \page architecture KDevelop Architecture
\section source_overview Platform Source Overview
\subsection what_is_platform Platform Vs. Others
The idea is that kdevplatform contains:
- Everything to create platform plugins
- Everything to create platform applications
- Commonly used/important plugins
.
\subsection platform_code Platform Code Layout
Platform consists of
- interfaces
- interfaces that expose everything necessary for plugins
.
- shell
- implements interfaces and basically provides a ready-to-use and extend application.
.
- sublime
- the user interface library
.
- project and language
- additional libraries for project managers and language supports (usable for other plugins as well)
.
- plugins
- common plugins, ie those that could be useful for other applications than just KDevelop
.
- outputview
- allows plugins to easily implement toolviews that show line-by-line output. for example make build output.
.
- vcs
- central & distributed version control library
.
.
\subsection platform_for_plugins What to Use in Plugins
- Plugins need to link to the interfaces
- Plugins should never link to the shell
- Plugins should not need to link to the sublime library
- Plugins can optionally link to other helper libraries in platform when necessary
.
\subsection platform_coding_conventions Current Platform Coding Conventions
- The preferred coding style is http://www.kdevelop.org/mediawiki/index.php/Coding_Guidelines
- All platform classes shall be in the KDevelop namespace
- All files should be named without a kdev prefix
- All files have to be installed in subdirectories under ${INCLUDE_INSTALL_DIR}/kdevplatform/
- All interface names should start with an I (e.g. KDevelop::IFoo) and the files should be named ifoo.h and ifoo.cpp
- All interface implementations are named KDevelop::Foo and their files foo.h/cpp
.
\section code_overview Platform Code Overview
\subsection core ICore/Core
Core is the central class. It provides access to all shell functionality.
There's a KDevelop::ICore interface and a KDevelop::Core implementation.
The KDevelop::ICore interface gives access to all controllers exposed via
interfaces. Each plugin is initialized with an KDevelop::ICore pointer so
it always has access to the core via the KDevelop::IPlugin::core method.
Core is a singleton that needs to be manually initialized by platform
applications using \ref KDevelop::Core::initialize right after the KApplication
object is created and ShellExtension is initialized. KDevelop::ShellExtension
the mechanism platform applications define which UI configuration files are
used and what the default profile is. Use KDevelop::ShellExtension::defaultArea
to define the name of default UI area (see below for more information about areas).
\subsection plugin IPlugin
All concrete plugins must inherit from KDevelop::IPlugin. Extension interfaces
can be used to handle groups of similar plugins in a generic fashion, see below.
Each plugin must have an associated .desktop file, as described in the
KPluginInfo class documentation.
\subsection extensions Extension Interfaces and Plugins
The idea behind extension interfaces is to provide following features:
- To allow plugins to expose their functionality via abstract interfaces
and at the same time allow them to not care about binary compatibility
and to not force a plugin to link against another plugin.\n \n The documentation
plugin would be a good example of a piece of non-core functionality
that could be exported via an IDocumentation extension interface with methods like
lookupInDocumentation(const QString &) and others. The outputview plugin with
has an IOutputView interface which is another example of such a use-case. The
IOutputView interface provided by the outputview plugin can be used by other
plugins to display information to the user with the other plugin not caring
about the specific implementation details of the outputview plugin
- To provide implementations of important functionality in plugins.\n \n
Good examples are buildsystem managers, builders, and language supports in
KDevelop4. When a project is opened, a buildsystem manager plugin (that
implements either IBuildSystemManager interface or IProjectFileManager) is
looked for by the shell and loaded.
- To forget about binary compatibility issues of dependent plugins.\n \n
In case the plugin has something new to expose, a new version of the
extension interface has to be defined. All other plugins will either continue
using the old interface or ask for the new one.
.
The extension interfaces framework is implemented with QExtensionManager and
company. See the Qt documentation for more information.
KDevelop::IBuildSystemManager is an example of an extension interface.
Plugins that implement this provide build and/or compile functionality.
An other example is KDevelop::ILanguageSupport which abstracts away language
specific details from shell.
\subsubsection declare_extension To declare an extension interface:
- Create an abstract class
- Add macros declaring the interface to your abstract class' header file
- If your abstract class is in a namespace use the following:
\code
Q_DECLARE_INTERFACE( KDevelop::IMyInterface, "org.kdevelop.IMyInterface" )
\endcode
- Otherwise use:
\code
Q_DECLARE_INTERFACE( IMyInterface, "org.kdevelop.IMyInterface" )
\endcode
.
\note The use of nested namespaces is not supported.
.
\subsubsection implement_extension To implement an extension interface:
- Create a class that inherits from KDevelop::IPlugin
- Add the interface you wish to implement to the inheritance chain for your
new plugin class (use multiple inheritance here)
- Add a Q_INTERFACES macro to your class declaration. \n \n It should be in the
same section as the Q_OBJECT macro. The argument to the Q_INTERFACES macro
should be the name of the interface you're inheriting from. \n \n For
example, if one is using the IMyInterface interface, one would add
\code
Q_INTERFACES( IMyInterface )
\endcode
to one's class declaration.
-- Use KDEV_USE_EXTENSION_INTERFACE macro in the constructor of the plugin:
- \code
- KDEV_USE_EXTENSION_INTERFACE( IMyInterface )
- \endcode
- Add the following to the plugin's .desktop file:
\code
X-KDevelop-Interfaces=IMyInterface
\endcode
Replacing IMyInterface which the class name of one's interface.
.
Here's an example putting all of the above together:
\code
class MyPlugin: public KDevelop::IPlugin, public KDevelop::IMyInterface
{
Q_OBJECT
Q_INTERFACES(KDevelop::IMyInterface)
public:
MyPlugin(QObject* parent) : IPlugin( parent )
{
- KDEV_USE_EXTENSION_INTERFACE( KDevelop::IMyInterface )
}
};
\endcode
\subsubsection load_extension To load a plugin that supports a certain extension interface:
- Use KDevelop::IPluginController::pluginForExtension if you have only one
possible plugin implementing the extension interface
- Or use KDevelop::IPluginController::allPluginsForExtension to return a list
of plugins. \n \note The above methods will load plugins to verify they implement the
extension being asked for if needed.
- Once an KDevelop::IPlugin pointer is returned by one of two methods above,
it can be asked for an extension using KDevelop::IPlugin::extension
.
\subsubsection interplugin_dependency To set a dependency between interfaces:
- Set the list of required interfaces in plugin's .desktop file
\code
X-KDevelop-IRequired=IMyInterface,IMyOtherInterface
\endcode
.
It is not possible to set direct inter-plugin dependencies. One plugin shall
never depend on another plugin, it shall only depend on something that
implements an interface.
\subsection project Project Management Infrastructure
KDevelop4 can have multiple projects open at any given time.
Project management structure:
\code
|--------------------| |-------------------------------|
|---->| KDevelop::IProject |------>| KDevelop::IProjectFileManager |
| |--------------------| |-------------------------------|
| |
|------------------------------| | |------------------------|
| KDevelop::IProjectController |------->| KDevelop::ProjectModel |
|------------------------------| | |------------------------|
| |
| |--------------------| |-------------------------------|
|---->| KDevelop::IProject |------>| KDevelop::IBuildSystemManager |
|--------------------| |-------------------------------|
\endcode
ProjectController is a container for projects (KDevelop::IProject interface). Each project contributes
its contents (files, folders, targets, etc.) to the KDevelop::ProjectModel. Each project also has
a file manager plugin associated with it.
A project file manager is the plugin which implements either one of two
interfaces: KDevelop::IProjectFileManager or KDevelop::IBuildSystemManager. The
project file manager provides the actual project management facilities.
Plugins access project contents through classes defined in
KDevelop::ProjectModel. Examples include ProjectFolderItem, ProjectFileItem,
ProjectTargetItem, etc.
%KDevelop currently supports the notion of "current project" (the one which is
currently selected in the project management view). But plugins are encouraged
to not depend on the notion of a current project. The selection might be empty
or the project management view might be closed at any time.
%KDevelop Platform provides by default a generic project manager. This project
manager treats all files and subdirectories under the project directory as
project items and currently provides no building facilities.
The project file (<projectname>.kdev4) controls which project file
manager will be loaded. For example, this .kdev4 file will load the generic
manager:
\code
[General Options]
Manager=KDevGenericManager
\endcode
\subsection language Language Support Infrastructure
The language support infrastructure is designed to be similar to project management.
Its goals are:
- use as many language supports as necessary at the same time
- be able to load several language supports for one source file
good examples are mixed-source files like .php (php+html), .rhtml (ruby + html)
- be not dependent on projects
.
language support structure:
\code
|----------------------------|
|---->| KDevelop::ILanguageSupport |
|---------------------| | |----------------------------|
|---->| KDevelop::ILanguage |----|
| |---------------------| | |----------------------------|
| |---->| KDevelop::BackgroundParser |
|-------------------------------| | |----------------------------|
| KDevelop::ILanguageController |--|
|-------------------------------| | |----------------------------|
| |---->| KDevelop::ILanguageSupport |
| |---------------------| | |----------------------------|
|---->| KDevelop::ILanguage |----|
|---------------------| | |----------------------------|
|---->| KDevelop::BackgroundParser |
|----------------------------|
\endcode
Language controller holds the set of already loaded languages and provides means to load more.
For each language (defined by its "name") Language object exists. Each such language has
a background parser and a actual support plugin that implements KDevelop::ILanguageSupport.
This way the basic shell functionality (like language loading algorithm and background parser)
is separated from language-specific stuff (like parsing).
Unlike KDevelop3, language support plugin is loaded not among with a project. Instead, when the
source file is opened, the language controller asks plugin controller whether there are
any language plugins (those that implement KDevelop::ILanguageSupport) that support a mime type
of the file (those who set X-KDevelop-SupportedMimeTypes=...).
For each language support plugin found, the KDevelop::Language object (that implements KDevelop::ILanguage interface)
is created and language support plugin is associated with it. Then each language is asked to return
a KDevelop::ParseJob to process the file. This way several language supports are able to parse one file.
If KDevelop::Language object for given mimetype already exists, it is used and no plugin loading is performed.
\subsection uicontroller IUiController
KDevelop::UiController is closely connected to the Sublime UI and basically is an subclass of
\ref Sublime::Controller which will be explained later.
The main job of UiController is to allow plugins to manager their views and toolviews. Currently only
toolviews can be added and removed.
Sublime UI wants plugins to add and remove not actual toolviews, but factories to create them.
This is because user can request from Sublime UI to show a new toolview at any time. For example,
it is possible to have more than one Konsole toolviews. The user just have to ask for them.
Automatically factory will be used only once, when UI controller is asked to add a toolview.
This means for example that only one Konsole toolview will be opened automatically.
To create a factory, a plugin needs to subclass KDevelop::IToolViewFactory and implement two methods:
- virtual QWidget* KDevelop::IToolViewFactory::create(QWidget *parent = 0)
where the actual toolview widget will be created
- virtual Qt::DockWidgetArea KDevelop::IToolViewFactory::defaultPosition(const QString &areaName)
which will give the UI a hint on where to place the toolview
.
Once the factory is ready, it has to be added to the UiController using IUiController::addToolView() method.
It is not absolutely necessary for a plugin to remove or delete toolview factory. UiController will
delete them all upon unload.
NOTE: temporarily IUiController has KDevelop::IUiController::openUrl() method which is the only
way to open files in KDevelop until DocumentController is ported.
\subsection launch Launch Framework
- Launch Configuration (\ref KDevelop::ILaunchConfiguration)
one entry created by the user in Configure Launches
(ie an executable)
- has one Launch Configuration Type
- has a Launcher for every possible Launch Mode
- Launch Configuration Type (\ref KDevelop::LaunchConfigurationType)
native application / script
- has possible Launchers
- Launcher (\ref KDevelop::ILauncher)
creates the KJob for launching
- has LaunchConfigurationPageFactories
- supports a number of Launch Modes
- Launch Mode: execute / debug / profile
- Launch Configuration Page Factory (\ref KDevelop::LaunchConfigurationPageFactory)
creates Widget used in Configure Launches
- Launch Configuration Page (\ref KDevelop::LaunchConfigurationPage)
Widget that configures a LaunchConfiguration
- RunController (\ref KDevelop::IRunController)
- has possible Launch Configuration Types
\section sublime Sublime UI
\subsection sublime_operation Modus Operandi
- UI provides support for "areas" (alike Eclipse's perspectives)
- The basic set of areas is:
- code editing area with split editor windows \n
(with kate/konqueror-like splitting)
- debugging area \n
(Xcode-like debugger window with only one editor view by default but with possiblility to show more)
- profiling area \n
(like KCacheGrind)
- user interface design area \n
(like Qt designer)
- Area configuration includes code editor windows (unlike eclipse)
- Each area can be displayed in usual Qt mainwindow with toolviews in dockwidgets
- Areas are shown in separate mainwindows so that multiple-monitor setups become the best supported
- One area can be shown in two and more mainwindows by "cloning" the area \n
(unlike Eclipse that pretends that two mainwindows show the same area)
- Optionally areas can be switched inside the same mainwindow without opening new ones
(like in Eclipse), but this mode of operation is currently not implemented. \n
Also Sublime was not optimized for this use-case. See wiki pages for a detailed discussion and
explanation of possible problems.
- It is possible to open as many similar toolviews as necessary (for example, several konsole's)
- It is possible to open several views for the same file in code editor view (unlike KDevelop 3.x)
- Instead of tabs for editor windows a "switcher" is provided. \n
Currently the switcher is a combobox but that will change for sure.
.
\subsection sublime_architecture Brief Architecture Overview
Sublime::Controller is the central point of the whole Sublime infrastructure. It contains areas,
documents, and controlls mainwindows. \n
Sublime::Controller::showArea() is the only way to show an area in the mainwindow.
Sublime::MainWindow is just a KParts::MainWindow which knows how to "reconstruct" the area
and react on area changes. Additionally it knows which view and toolview is "active" (i.e. last focused).
Sublime::Area is the object that controls views, toolviews, and defines their placement
inside the mainwindow. Provides various view management (e.g. add/remove/etc.) methods and signals.
Also Area is responsible for storing and restoring the view layout (on-disk storage is not
implemented currently).
An area is identified by its name. Each KDevelop platform application, for example, has to define
a concept of a "default area" in ShellExtension so that the UiController can load it by default.
While areas operate on views, the Sublime Controller deals with Sublime::Document objects only.
Sublime::View is only a thin wrapper around QWidget.
Document is what provides views. Sublime::Document::createView() is the only way to get a view.
When createView() is called, the protected Sublime::Document::createViewWidget() is called to
return the actual widget for a view.
There is an abstract Sublime::Document class with no createViewWidget() implementation and
there's a convenience class Sublime::ToolDocument that can create widgets of user-specified type.
KDevelop currently uses Sublime::PartDocument which is subclassed by KDevelop::PartDocument.
This PartDocument creates a view by loading a part and then asking a part about its widget.
*/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ec120108d2..159a4de2ce 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,174 +1,174 @@
# kdevplatform version
set(KDEVPLATFORM_VERSION_MAJOR 5)
set(KDEVPLATFORM_VERSION_MINOR 0)
set(KDEVPLATFORM_VERSION_PATCH 3)
# plugin versions listed in the .desktop files
-set(KDEV_PLUGIN_VERSION 26)
+set(KDEV_PLUGIN_VERSION 27)
# Increase this to reset incompatible item-repositories
set(KDEV_ITEMREPOSITORY_VERSION 86)
# library version / SO version
set(KDEVPLATFORM_LIB_VERSION 10.0.0)
set(KDEVPLATFORM_LIB_SOVERSION 10)
################################################################################
cmake_minimum_required(VERSION 2.8.12)
project(KDevPlatform)
# we need some parts of the ECM CMake helpers
find_package (ECM 0.0.9 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${KDevPlatform_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH})
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddTests)
include(ECMOptionalAddSubdirectory)
include(ECMInstallIcons)
include(ECMSetupVersion)
include(ECMMarkAsTest)
include(ECMMarkNonGuiExecutable)
include(ECMGenerateHeaders)
include(ECMPackageConfigHelpers)
include(GenerateExportHeader)
include(FeatureSummary)
include(WriteBasicConfigVersionFile)
include(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDevPlatformMacros)
set(QT_MIN_VERSION "5.4.0")
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core DBus Widgets WebKitWidgets Concurrent Test)
find_package(Qt5QuickWidgets ${QT_MIN_VERSION})
set_package_properties(Qt5QuickWidgets PROPERTIES
PURPOSE "Qt5 QuickWidgets library (part of Qt >=5.3). Required for the Welcome Page plugin."
)
set(KF5_DEP_VERSION "5.16.0")
find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS
Archive
Config
GuiAddons
WidgetsAddons
IconThemes
I18n
ItemModels
ItemViews
JobWidgets
KCMUtils
KIO
NewStuff
Notifications
NotifyConfig
Parts
Service
Sonnet
TextEditor
ThreadWeaver
WindowSystem
Declarative
XmlGui
)
find_package(Grantlee5)
set_package_properties(Grantlee5 PROPERTIES
PURPOSE "Grantlee templating library, needed for file templates"
URL "http://www.grantlee.org/"
TYPE RECOMMENDED)
set(Boost_ADDITIONAL_VERSIONS 1.39.0 1.39)
find_package(Boost 1.35.0)
set_package_properties(Boost PROPERTIES
PURPOSE "Boost libraries for enabling the classbrowser"
URL "http://www.boost.org"
TYPE REQUIRED)
add_definitions(
-DQT_DEPRECATED_WARNINGS
-DQT_DISABLE_DEPRECATED_BEFORE=0x050400
-DQT_NO_URL_CAST_FROM_STRING
-DQT_STRICT_ITERATORS
-DQT_USE_FAST_CONCATENATION
-DQT_USE_FAST_OPERATOR_PLUS
)
# Turn off missing-field-initializers warning for GCC to avoid noise from false positives with empty {}
# See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html
check_cxx_compiler_flag(-Wno-missing-field-initializers HAVE_MFI_FLAG)
check_cxx_compiler_flag(-Werror=undefined-bool-conversion HAVE_UBC_FLAG)
check_cxx_compiler_flag(-Werror=tautological-undefined-compare HAVE_TUC_FLAG)
if (HAVE_MFI_FLAG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers")
endif()
if (HAVE_UBC_FLAG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=undefined-bool-conversion")
endif()
if (HAVE_TUC_FLAG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=tautological-undefined-compare")
endif()
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-kdevplatform.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/config-kdevplatform.h )
include_directories(${KDevPlatform_SOURCE_DIR} ${KDevPlatform_BINARY_DIR})
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER)
if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug"
OR CMAKE_BUILD_TYPE_TOLOWER STREQUAL "")
set(COMPILER_OPTIMIZATIONS_DISABLED TRUE)
else()
set(COMPILER_OPTIMIZATIONS_DISABLED FALSE)
endif()
add_subdirectory(sublime)
add_subdirectory(interfaces)
add_subdirectory(project)
add_subdirectory(language)
add_subdirectory(shell)
add_subdirectory(util)
add_subdirectory(outputview)
add_subdirectory(vcs)
add_subdirectory(pics)
add_subdirectory(debugger)
add_subdirectory(documentation)
add_subdirectory(serialization)
add_subdirectory(template)
add_subdirectory(tests)
add_subdirectory(plugins)
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevPlatform")
ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevPlatformConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
ecm_setup_version(${KDEVPLATFORM_VERSION_MAJOR}.${KDEVPLATFORM_VERSION_MINOR}.${KDEVPLATFORM_VERSION_PATCH}
VARIABLE_PREFIX KDEVPLATFORM
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevplatform_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevPlatformConfigVersion.cmake"
SOVERSION ${KDEVPLATFORM_LIB_SOVERSION})
install( FILES
"${KDevPlatform_BINARY_DIR}/kdevplatform_version.h"
"${KDevPlatform_BINARY_DIR}/config-kdevplatform.h"
DESTINATION "${KDE_INSTALL_INCLUDEDIR}/kdevplatform" )
install( FILES
"${KDevPlatform_BINARY_DIR}/KDevPlatformConfig.cmake"
"${KDevPlatform_BINARY_DIR}/KDevPlatformConfigVersion.cmake"
cmake/modules/KDevPlatformMacros.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}" )
install( EXPORT KDevPlatformTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
NAMESPACE KDev::
FILE KDevPlatformTargets.cmake )
include(CTest)
# CTestCustom.cmake has to be in the CTEST_BINARY_DIR.
# in the KDE build system, this is the same as CMAKE_BINARY_DIR.
configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/interfaces/iplugin.cpp b/interfaces/iplugin.cpp
index 7a78a1126b..a96dfffd94 100644
--- a/interfaces/iplugin.cpp
+++ b/interfaces/iplugin.cpp
@@ -1,219 +1,208 @@
/* This file is part of the KDE project
Copyright 2002 Simon Hausmann
Copyright 2002 Matthias Hoelzer-Kluepfel
Copyright 2002 Harald Fernengel
Copyright 2002 Falk Brettschneider
Copyright 2003 Julian Rockey
Copyright 2003 Roberto Raggi
Copyright 2003 Jens Dagerbo
Copyright 2003 Mario Scalas
Copyright 2003-2004,2007 Alexander Dymo
Copyright 2006 Adam Treat
Copyright 2007 Andreas Pakulat
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 "iplugin.h"
#include
#include
#include
#include
#include "icore.h"
#include "iplugincontroller.h"
#include "iprojectcontroller.h"
#include "iproject.h"
#include "contextmenuextension.h"
namespace KDevelop
{
class IPluginPrivate
{
public:
IPluginPrivate(IPlugin *q)
: q(q)
{}
~IPluginPrivate()
{
}
void guiClientAdded(KXMLGUIClient *client)
{
if (client != q)
return;
q->initializeGuiState();
updateState();
}
void updateState()
{
const int projectCount =
ICore::self()->projectController()->projectCount();
IPlugin::ReverseStateChange reverse = IPlugin::StateNoReverse;
if (! projectCount)
reverse = IPlugin::StateReverse;
q->stateChanged(QStringLiteral("has_project"), reverse);
}
IPlugin *q;
ICore *core;
- QVector m_extensions;
QString m_errorDescription;
};
IPlugin::IPlugin( const QString &componentName, QObject *parent )
: QObject(parent)
, KXMLGUIClient()
, d(new IPluginPrivate(this))
{
// The following cast is safe, there's no component in KDevPlatform that
// creates plugins except the plugincontroller. The controller passes
// Core::self() as parent to KServiceTypeTrader::createInstanceFromQuery
// so we know the parent is always a Core* pointer.
// This is the only way to pass the Core pointer to the plugin during its
// creation so plugins have access to ICore during their creation.
Q_ASSERT(qobject_cast(parent));
d->core = static_cast(parent);
setComponentName(componentName, componentName);
auto clientAdded = [=] (KXMLGUIClient* client) {
d->guiClientAdded(client);
};
foreach (KMainWindow* mw, KMainWindow::memberList()) {
KXmlGuiWindow* guiWindow = qobject_cast(mw);
if (! guiWindow)
continue;
connect(guiWindow->guiFactory(), &KXMLGUIFactory::clientAdded,
this, clientAdded);
}
auto updateState = [=] { d->updateState(); };
connect(ICore::self()->projectController(), &IProjectController::projectOpened,
this, updateState);
connect(ICore::self()->projectController(), &IProjectController::projectClosed,
this, updateState);
}
IPlugin::~IPlugin()
{
delete d;
}
void IPlugin::unload()
{
}
ICore *IPlugin::core() const
{
return d->core;
}
}
-QVector KDevelop::IPlugin::extensions( ) const
-{
- return d->m_extensions;
-}
-
-void KDevelop::IPlugin::addExtension( const QByteArray& ext )
-{
- d->m_extensions << ext;
-}
-
KDevelop::ContextMenuExtension KDevelop::IPlugin::contextMenuExtension(
KDevelop::Context* )
{
return KDevelop::ContextMenuExtension();
}
void KDevelop::IPlugin::initializeGuiState()
{ }
class CustomXmlGUIClient : public KXMLGUIClient
{
public:
CustomXmlGUIClient(const QString& componentName)
{
// TODO KF5: Get rid off this
setComponentName(componentName, componentName);
}
using KXMLGUIClient::setXMLFile;
};
KXMLGUIClient* KDevelop::IPlugin::createGUIForMainWindow(Sublime::MainWindow* window)
{
QScopedPointer ret(new CustomXmlGUIClient(componentName()));
QString file;
createActionsForMainWindow(window, file, *ret->actionCollection());
if (ret->actionCollection()->isEmpty()) {
return nullptr;
}
Q_ASSERT(!file.isEmpty()); //A file must have been set
ret->setXMLFile(file);
return ret.take();
}
void KDevelop::IPlugin::createActionsForMainWindow( Sublime::MainWindow* /*window*/, QString& /*xmlFile*/, KActionCollection& /*actions*/ )
{
}
bool KDevelop::IPlugin::hasError() const
{
return !d->m_errorDescription.isEmpty();
}
void KDevelop::IPlugin::setErrorDescription(const QString& description)
{
d->m_errorDescription = description;
}
QString KDevelop::IPlugin::errorDescription() const
{
return d->m_errorDescription;
}
int KDevelop::IPlugin::configPages() const
{
return 0;
}
KDevelop::ConfigPage* KDevelop::IPlugin::configPage (int, QWidget*)
{
return nullptr;
}
int KDevelop::IPlugin::perProjectConfigPages() const
{
return 0;
}
KDevelop::ConfigPage* KDevelop::IPlugin::perProjectConfigPage(int, const ProjectConfigOptions&, QWidget*)
{
return nullptr;
}
#include "moc_iplugin.cpp"
diff --git a/interfaces/iplugin.h b/interfaces/iplugin.h
index 73985c98cb..7eb3138031 100644
--- a/interfaces/iplugin.h
+++ b/interfaces/iplugin.h
@@ -1,285 +1,281 @@
/* This file is part of the KDE project
Copyright 1999-2001 Bernd Gehrmann
Copyright 2004,2007 Alexander Dymo
Copyright 2006 Adam Treat
Copyright 2007 Andreas Pakulat
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_IPLUGIN_H
#define KDEVPLATFORM_IPLUGIN_H
#include
#include
#include
#include "interfacesexport.h"
namespace Sublime {
class MainWindow;
}
/**
* This macro adds an extension interface to register with the extension manager
* Call this macro for all interfaces your plugin implements in its constructor
*/
#define KDEV_USE_EXTENSION_INTERFACE(Extension) \
- addExtension(QByteArray::fromRawData(qobject_interface_iid(), \
- static_cast(strlen(qobject_interface_iid()))));
+ _Pragma("message(\"Using deprecated function: KDEV_USE_EXTENSION_INTERFACE. Just remove the use of it.\")")
namespace KDevelop
{
class ICore;
class ConfigPage;
class Context;
class ContextMenuExtension;
struct ProjectConfigOptions;
/**
* The base class for all KDevelop plugins.
*
* Plugin is a component which is loaded into KDevelop shell at startup or by
* request. Each plugin should have corresponding .desktop file with a
* description. The .desktop file template looks like:
* @code
* [Desktop Entry]
* Type=Service
* Exec=blubb
* Name=
* GenericName=
* Comment=
* Icon=
* ServiceTypes=KDevelop/Plugin
* X-KDE-Library=
* X-KDE-PluginInfo-Name=
* X-KDE-PluginInfo-Author=
* X-KDE-PluginInfo-Version=
* X-KDE-PluginInfo-License=
* X-KDE-PluginInfo-Category=
* X-KDevelop-Version=
* X-KDevelop-Category=
* X-KDevelop-Mode=GUI
* X-KDevelop-LoadMode=
* X-KDevelop-Language=
* X-KDevelop-SupportedMimeTypes=
* X-KDevelop-Interfaces=
* X-KDevelop-IOptional=
* X-KDevelop-IRequired=
* @endcode
* Description of parameters in .desktop file:
* - Name is a translatable name of a plugin, it is used in the plugin
* selection list (required);
* - GenericName is a translatable generic name of a plugin, it should
* describe in general what the plugin can do (required);
* - Comment is a short description about the plugin (optional);
* - Icon is a plugin icon (preferred);
* X-KDE-librarythis is the name of the .so file to load for this plugin (required);
* - X-KDE-PluginInfo-Name is a non-translatable user-readable plugin
* identifier used in KTrader queries (required);
* - X-KDE-PluginInfo-Author is a non-translateable name of the plugin
* author (optional);
* - X-KDE-PluginInfo-Version is version number of the plugin (optional);
* - X-KDE-PluginInfo-License is a license (optional). can be: GPL,
* LGPL, BSD, Artistic, QPL or Custom. If this property is not set, license is
* considered as unknown;
* - X-KDE-PluginInfo-Category is used to categorize plugins (optional). can be:
* Core, Project Management, Version Control, Utilities, Documentation,
* Language Support, Debugging, Other
* If this property is not set, "Other" is assumed
* - X-KDevelop-Version is the KDevPlatform API version this plugin
* works with (required);
* - X-KDevelop-Interfaces is a list of extension interfaces that this
* plugin implements (optional);
* - X-KDevelop-IRequired is a list of extension interfaces that this
* plugin depends on (optional); A list entry can also be of the form @c interface@pluginname,
* in which case a plugin of the given name is required which implements the interface.
* - X-KDevelop-IOptional is a list of extension interfaces that this
* plugin will use if they are available (optional);
* - X-KDevelop-Language is the name of the language the plugin provides
* support for (optional);
* - X-KDevelop-SupportedMimeTypes is a list of mimetypes that the
* language-parser in this plugin supports (optional);
* - X-KDevelop-Mode is either GUI or NoGUI to indicate whether a plugin can run
* with the GUI components loaded or not (required);
* - X-KDevelop-Category is a scope of a plugin (see below for
* explanation) (required);
* - X-KDevelop-LoadMode can be set to AlwaysOn in which case the plugin will
* never be unloaded even if requested via the API. (optional);
*
* Plugin scope can be either:
* - Global
* - Project
* .
* Global plugins are plugins which require only the shell to be loaded and do not operate on
* the Project interface and/or do not use project wide information.\n
* Core plugins are global plugins which offer some important "core" functionality and thus
* are not selectable by user in plugin configuration pages.\n
* Project plugins require a project to be loaded and are usually loaded/unloaded along with
* the project.
* If your plugin uses the Project interface and/or operates on project-related
* information then this is a project plugin.
*
*
* @sa Core class documentation for information about features available to
* plugins from shell applications.
*/
class KDEVPLATFORMINTERFACES_EXPORT IPlugin: public QObject, public KXMLGUIClient
{
Q_OBJECT
public:
/**Constructs a plugin.
* @param componentName The component name for this plugin.
* @param parent The parent object for the plugin.
*/
IPlugin(const QString &componentName, QObject *parent);
/**Destructs a plugin.*/
~IPlugin() override;
/**
* Signal the plugin that it should cleanup since it will be unloaded soon.
*/
Q_SCRIPTABLE virtual void unload();
/**
* Provides access to the ICore implementation
*/
Q_SCRIPTABLE ICore *core() const;
- Q_SCRIPTABLE QVector extensions() const;
-
- template Extension* extension()
+ /**
+ * Convenience API to access an interface inherited by this plugin
+ *
+ * @return Instance to the specified interface, or nullptr
+ */
+ template
+ inline Extension* extension()
{
- const auto extensionIID = QByteArray::fromRawData(qobject_interface_iid(),
- static_cast(strlen(qobject_interface_iid())));
- if (extensions().contains(extensionIID)) {
- return qobject_cast(this);
- }
- return nullptr;
+ return qobject_cast(this);
}
/**
* Ask the plugin for a ContextActionContainer, which contains actions
* that will be merged into the context menu.
* @param context the context describing where the context menu was requested
* @returns a container describing which actions to merge into which context menu part
*/
virtual ContextMenuExtension contextMenuExtension( KDevelop::Context* context );
/**
* Can create a new KXMLGUIClient, and set it up correctly with all the plugins per-window GUI actions.
*
* The caller owns the created object, including all contained actions. The object is destroyed as soon as
* the mainwindow is closed.
*
* The default implementation calls the convenience function @ref createActionsForMainWindow and uses it to fill a custom KXMLGUIClient.
*
* Only override this version if you need more specific features of KXMLGUIClient, or other special per-window handling.
*
* @param window The main window the actions are created for
*/
virtual KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window );
/**
* This function allows allows setting up plugin actions conveniently. Unless createGUIForMainWindow was overridden,
* this is called for each new mainwindow, and the plugin should add its actions to @p actions, and write its KXMLGui xml file
* into @p xmlFile.
*
* @param window The main window the actions are created for
* @param xmlFile Change this to the xml file that needed for merging the actions into the GUI
* @param actions Add your actions here. A new set of actions has to be created for each mainwindow.
*/
virtual void createActionsForMainWindow( Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions );
/**
* This function is necessary because there is no proper way to signal errors from plugin constructor.
* @returns True if the plugin has encountered an error (and therefore has an error description),
* false otherwise.
*/
bool hasError() const;
/**
* Description of the last encountered error, of an empty string if none.
*/
QString errorDescription() const;
/**
* Set a @p description of the errror encountered. An empty error
* description implies no error in the plugin.
*/
void setErrorDescription(QString const& description);
/**
* Get the global config page with the \p number, config pages from 0 to
* configPages()-1 are available if configPages() > 0.
*
* @param number index of config page
* @param parent parent widget for config page
* @return newly created config page or NULL, if the number is out of bounds, default implementation returns NULL.
* This config page should inherit from ProjectConfigPage, but it is not a strict requirement.
* The default implementation returns @c nullptr.
* @see perProjectConfigPages(), ProjectConfigPage
*/
virtual ConfigPage* configPage(int number, QWidget *parent);
/**
* Get the number of available config pages for global settings.
* @return number of global config pages. The default implementation returns zero.
* @see configPage()
*/
virtual int configPages() const;
/**
* Get the number of available config pages for per project settings.
* @return number of per project config pages. The default implementation returns zero.
* @see perProjectConfigPage()
*/
virtual int perProjectConfigPages() const;
/**
* Get the per project config page with the \p number, config pages from 0 to
* perProjectConfigPages()-1 are available if perProjectConfigPages() > 0.
*
* @param number index of config page
* @param options The options used to initialize the ProjectConfigPage
* @param parent parent widget for config page
* @return newly created config page or NULL, if the number is out of bounds, default implementation returns NULL.
* This config page should inherit from ProjectConfigPage, but it is not a strict requirement.
* The default implementation returns @c nullptr.
* @see perProjectConfigPages(), ProjectConfigPage
*/
virtual ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent);
protected:
- void addExtension( const QByteArray& );
-
/**
* Initialize the XML GUI State.
*/
virtual void initializeGuiState();
private:
friend class IPluginPrivate;
class IPluginPrivate* const d;
};
}
#endif
diff --git a/plugins/appwizard/appwizardplugin.cpp b/plugins/appwizard/appwizardplugin.cpp
index 59d17a5e0d..ed6274218f 100644
--- a/plugins/appwizard/appwizardplugin.cpp
+++ b/plugins/appwizard/appwizardplugin.cpp
@@ -1,516 +1,515 @@
/***************************************************************************
* Copyright 2001 Bernd Gehrmann *
* Copyright 2004-2005 Sascha Cunz *
* Copyright 2005 Ian Reinhart Geiser *
* Copyright 2007 Alexander Dymo *
* Copyright 2008 Evgeniy Ivanov *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "appwizardplugin.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "appwizarddialog.h"
#include "projectselectionpage.h"
#include "projectvcspage.h"
#include "projecttemplatesmodel.h"
#include "debug.h"
using namespace KDevelop;
Q_LOGGING_CATEGORY(PLUGIN_APPWIZARD, "kdevplatform.plugins.appwizard")
K_PLUGIN_FACTORY_WITH_JSON(AppWizardFactory, "kdevappwizard.json", registerPlugin();)
AppWizardPlugin::AppWizardPlugin(QObject *parent, const QVariantList &)
: KDevelop::IPlugin(QStringLiteral("kdevappwizard"), parent)
, m_templatesModel(nullptr)
{
- KDEV_USE_EXTENSION_INTERFACE(KDevelop::ITemplateProvider);
setXMLFile(QStringLiteral("kdevappwizard.rc"));
m_newFromTemplate = actionCollection()->addAction(QStringLiteral("project_new"));
m_newFromTemplate->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template")));
m_newFromTemplate->setText(i18n("New From Template..."));
connect(m_newFromTemplate, &QAction::triggered, this, &AppWizardPlugin::slotNewProject);
m_newFromTemplate->setToolTip( i18n("Generate a new project from a template") );
m_newFromTemplate->setWhatsThis( i18n("This starts KDevelop's application wizard. "
"It helps you to generate a skeleton for your "
"application from a set of templates.") );
}
AppWizardPlugin::~AppWizardPlugin()
{
}
void AppWizardPlugin::slotNewProject()
{
model()->refresh();
AppWizardDialog dlg(core()->pluginController(), m_templatesModel);
if (dlg.exec() == QDialog::Accepted)
{
QString project = createProject( dlg.appInfo() );
if (!project.isEmpty())
{
core()->projectController()->openProject(QUrl::fromLocalFile(project));
KConfig templateConfig(dlg.appInfo().appTemplate);
KConfigGroup general(&templateConfig, "General");
QString file = general.readEntry("ShowFilesAfterGeneration");
if (!file.isEmpty())
{
file = KMacroExpander::expandMacros(file, m_variables);
core()->documentController()->openDocument(QUrl::fromUserInput(file));
}
} else {
KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n("Could not create project from template\n"), i18n("Failed to create project") );
}
}
}
namespace
{
IDistributedVersionControl* toDVCS(IPlugin* plugin)
{
Q_ASSERT(plugin);
return plugin->extension();
}
ICentralizedVersionControl* toCVCS(IPlugin* plugin)
{
Q_ASSERT(plugin);
return plugin->extension();
}
/*! Trouble while initializing version control. Show failure message to user. */
void vcsError(const QString &errorMsg, QTemporaryDir &tmpdir, const QUrl &dest, const QString &details = QString())
{
QString displayDetails = details;
if (displayDetails.isEmpty())
{
displayDetails = i18n("Please see the Version Control toolview");
}
KMessageBox::detailedError(nullptr, errorMsg, displayDetails, i18n("Version Control System Error"));
KIO::del(dest)->exec();
tmpdir.remove();
}
/*! Setup distributed version control for a new project defined by @p info. Use @p scratchArea for temporary files */
bool initializeDVCS(IDistributedVersionControl* dvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea)
{
Q_ASSERT(dvcs);
qCDebug(PLUGIN_APPWIZARD) << "DVCS system is used, just initializing DVCS";
const QUrl& dest = info.location;
//TODO: check if we want to handle KDevelop project files (like now) or only SRC dir
VcsJob* job = dvcs->init(dest);
if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
{
vcsError(i18n("Could not initialize DVCS repository"), scratchArea, dest);
return false;
}
qCDebug(PLUGIN_APPWIZARD) << "Initializing DVCS repository:" << dest;
job = dvcs->add({dest}, KDevelop::IBasicVersionControl::Recursive);
if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
{
vcsError(i18n("Could not add files to the DVCS repository"), scratchArea, dest);
return false;
}
job = dvcs->commit(QStringLiteral("initial project import from KDevelop"), {dest},
KDevelop::IBasicVersionControl::Recursive);
if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
{
vcsError(i18n("Could not import project into %1.", dvcs->name()), scratchArea, dest, job ? job->errorString() : QString());
return false;
}
return true; // We're good
}
/*! Setup version control for a new project defined by @p info. Use @p scratchArea for temporary files */
bool initializeCVCS(ICentralizedVersionControl* cvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea)
{
Q_ASSERT(cvcs);
qCDebug(PLUGIN_APPWIZARD) << "Importing" << info.sourceLocation << "to"
<< info.repository.repositoryServer();
VcsJob* job = cvcs->import( info.importCommitMessage, QUrl::fromLocalFile(scratchArea.path()), info.repository);
if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded )
{
vcsError(i18n("Could not import project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer()));
return false;
}
qCDebug(PLUGIN_APPWIZARD) << "Checking out";
job = cvcs->createWorkingCopy( info.repository, info.location, IBasicVersionControl::Recursive);
if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded )
{
vcsError(i18n("Could not checkout imported project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer()));
return false;
}
return true; // initialization phase complete
}
QString generateIdentifier( const QString& appname )
{
QString tmp = appname;
QRegExp re("[^a-zA-Z0-9_]");
return tmp.replace(re, QStringLiteral("_"));
}
} // end anonymous namespace
QString AppWizardPlugin::createProject(const ApplicationInfo& info)
{
QFileInfo templateInfo(info.appTemplate);
if (!templateInfo.exists()) {
qWarning() << "Project app template does not exist:" << info.appTemplate;
return QString();
}
QString templateName = templateInfo.baseName();
QString templateArchive;
const QStringList filters = {templateName + QStringLiteral(".*")};
const QStringList matchesPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevappwizard/templates/"), QStandardPaths::LocateDirectory);
foreach(const QString& matchesPath, matchesPaths) {
const QStringList files = QDir(matchesPath).entryList(filters);
if(!files.isEmpty()) {
templateArchive = matchesPath + files.first();
}
}
if(templateArchive.isEmpty()) {
qWarning() << "Template name does not exist in the template list";
return QString();
}
QUrl dest = info.location;
//prepare variable substitution hash
m_variables.clear();
m_variables[QStringLiteral("APPNAME")] = info.name;
m_variables[QStringLiteral("APPNAMEUC")] = info.name.toUpper();
m_variables[QStringLiteral("APPNAMELC")] = info.name.toLower();
m_variables[QStringLiteral("APPNAMEID")] = generateIdentifier(info.name);
m_variables[QStringLiteral("PROJECTDIR")] = dest.toLocalFile();
// backwards compatibility
m_variables[QStringLiteral("dest")] = m_variables[QStringLiteral("PROJECTDIR")];
m_variables[QStringLiteral("PROJECTDIRNAME")] = dest.fileName();
m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = info.vcsPluginName;
KArchive* arch = nullptr;
if( templateArchive.endsWith(QLatin1String(".zip")) )
{
arch = new KZip(templateArchive);
}
else
{
arch = new KTar(templateArchive, QStringLiteral("application/x-bzip"));
}
if (arch->open(QIODevice::ReadOnly))
{
QTemporaryDir tmpdir;
QString unpackDir = tmpdir.path(); //the default value for all Centralized VCS
IPlugin* plugin = core()->pluginController()->loadPlugin( info.vcsPluginName );
if( info.vcsPluginName.isEmpty() || ( plugin && plugin->extension() ) )
{
if( !QFileInfo::exists( dest.toLocalFile() ) )
{
QDir::root().mkpath( dest.toLocalFile() );
}
unpackDir = dest.toLocalFile(); //in DVCS we unpack template directly to the project's directory
}
else
{
QUrl url = KIO::upUrl(dest);
if(!QFileInfo::exists(url.toLocalFile())) {
QDir::root().mkpath(url.toLocalFile());
}
}
if ( !unpackArchive( arch->directory(), unpackDir ) )
{
QString errorMsg = i18n("Could not create new project");
vcsError(errorMsg, tmpdir, QUrl::fromLocalFile(unpackDir));
return QString();
}
if( !info.vcsPluginName.isEmpty() )
{
if (!plugin)
{
// Red Alert, serious program corruption.
// This should never happen, the vcs dialog presented a list of vcs
// systems and now the chosen system doesn't exist anymore??
tmpdir.remove();
return QString();
}
IDistributedVersionControl* dvcs = toDVCS(plugin);
ICentralizedVersionControl* cvcs = toCVCS(plugin);
bool success = false;
if (dvcs)
{
success = initializeDVCS(dvcs, info, tmpdir);
}
else if (cvcs)
{
success = initializeCVCS(cvcs, info, tmpdir);
}
else
{
if (KMessageBox::Continue ==
KMessageBox::warningContinueCancel(nullptr,
QStringLiteral("Failed to initialize version control system, "
"plugin is neither VCS nor DVCS.")))
success = true;
}
if (!success) return QString();
}
tmpdir.remove();
}else
{
qCDebug(PLUGIN_APPWIZARD) << "failed to open template archive";
return QString();
}
QString projectFileName = QDir::cleanPath( dest.toLocalFile() + '/' + info.name + ".kdev4" );
// Loop through the new project directory and try to detect the first .kdev4 file.
// If one is found this file will be used. So .kdev4 file can be stored in any subdirectory and the
// project templates can be more complex.
QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << QStringLiteral("*.kdev4"), QDir::NoFilter, QDirIterator::Subdirectories);
if(it.hasNext() == true)
{
projectFileName = it.next();
}
qCDebug(PLUGIN_APPWIZARD) << "Returning" << projectFileName << QFileInfo::exists( projectFileName ) ;
if( ! QFileInfo::exists( projectFileName ) )
{
qCDebug(PLUGIN_APPWIZARD) << "creating .kdev4 file";
KSharedConfigPtr cfg = KSharedConfig::openConfig( projectFileName, KConfig::SimpleConfig );
KConfigGroup project = cfg->group( "Project" );
project.writeEntry( "Name", info.name );
QString manager = QStringLiteral("KDevGenericManager");
QDir d( dest.toLocalFile() );
auto data = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager"));
foreach(const KPluginMetaData& info, data) {
QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter"));
if (!filter.isEmpty()) {
if (!d.entryList(filter).isEmpty()) {
manager = info.pluginId();
break;
}
}
}
project.writeEntry( "Manager", manager );
project.sync();
cfg->sync();
KConfigGroup project2 = cfg->group( "Project" );
qCDebug(PLUGIN_APPWIZARD) << "kdev4 file contents:" << project2.readEntry("Name", "") << project2.readEntry("Manager", "" );
}
return projectFileName;
}
bool AppWizardPlugin::unpackArchive(const KArchiveDirectory *dir, const QString &dest)
{
qCDebug(PLUGIN_APPWIZARD) << "unpacking dir:" << dir->name() << "to" << dest;
const QStringList entries = dir->entries();
qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(QStringLiteral(","));
//This extra tempdir is needed just for the files files have special names,
//which may contain macros also files contain content with macros. So the
//easiest way to extract the files from the archive and then rename them
//and replace the macros is to use a tempdir and copy the file (and
//replacing while copying). This also allows one to easily remove all files,
//by just unlinking the tempdir
QTemporaryDir tdir;
bool ret = true;
foreach (const QString& entry, entries)
{
if (entry.endsWith(QLatin1String(".kdevtemplate")))
continue;
if (dir->entry(entry)->isDirectory())
{
const KArchiveDirectory *file = (KArchiveDirectory *)dir->entry(entry);
QString newdest = dest + '/' + KMacroExpander::expandMacros(file->name(), m_variables);
if( !QFileInfo::exists( newdest ) )
{
QDir::root().mkdir( newdest );
}
ret |= unpackArchive(file, newdest);
}
else if (dir->entry(entry)->isFile())
{
const KArchiveFile *file = (KArchiveFile *)dir->entry(entry);
file->copyTo(tdir.path());
QString destName = dest + '/' + file->name();
if (!copyFileAndExpandMacros(QDir::cleanPath(tdir.path()+'/'+file->name()),
KMacroExpander::expandMacros(destName, m_variables)))
{
KMessageBox::sorry(nullptr, i18n("The file %1 cannot be created.", dest));
return false;
}
}
}
tdir.remove();
return ret;
}
bool AppWizardPlugin::copyFileAndExpandMacros(const QString &source, const QString &dest)
{
qCDebug(PLUGIN_APPWIZARD) << "copy:" << source << "to" << dest;
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(source);
if( !mime.inherits(QStringLiteral("text/plain")) )
{
KIO::CopyJob* job = KIO::copy( QUrl::fromUserInput(source), QUrl::fromUserInput(dest), KIO::HideProgressInfo );
if( !job->exec() )
{
return false;
}
return true;
} else
{
QFile inputFile(source);
QFile outputFile(dest);
if (inputFile.open(QFile::ReadOnly) && outputFile.open(QFile::WriteOnly))
{
QTextStream input(&inputFile);
input.setCodec(QTextCodec::codecForName("UTF-8"));
QTextStream output(&outputFile);
output.setCodec(QTextCodec::codecForName("UTF-8"));
while(!input.atEnd())
{
QString line = input.readLine();
output << KMacroExpander::expandMacros(line, m_variables) << "\n";
}
#ifndef Q_OS_WIN
// Preserve file mode...
QT_STATBUF statBuf;
QT_FSTAT(inputFile.handle(), &statBuf);
// Unix only, won't work in Windows, maybe KIO::chmod could be used
::fchmod(outputFile.handle(), statBuf.st_mode);
#endif
return true;
}
else
{
inputFile.close();
outputFile.close();
return false;
}
}
}
KDevelop::ContextMenuExtension AppWizardPlugin::contextMenuExtension(KDevelop::Context* context)
{
KDevelop::ContextMenuExtension ext;
if ( context->type() != KDevelop::Context::ProjectItemContext || !static_cast(context)->items().isEmpty() ) {
return ext;
}
ext.addAction(KDevelop::ContextMenuExtension::ProjectGroup, m_newFromTemplate);
return ext;
}
ProjectTemplatesModel* AppWizardPlugin::model()
{
if(!m_templatesModel)
m_templatesModel = new ProjectTemplatesModel(this);
return m_templatesModel;
}
QAbstractItemModel* AppWizardPlugin::templatesModel()
{
return model();
}
QString AppWizardPlugin::knsConfigurationFile() const
{
return QStringLiteral("kdevappwizard.knsrc");
}
QStringList AppWizardPlugin::supportedMimeTypes() const
{
QStringList types;
types << QStringLiteral("application/x-desktop");
types << QStringLiteral("application/x-bzip-compressed-tar");
types << QStringLiteral("application/zip");
return types;
}
QIcon AppWizardPlugin::icon() const
{
return QIcon::fromTheme(QStringLiteral("project-development-new-template"));
}
QString AppWizardPlugin::name() const
{
return i18n("Project Templates");
}
void AppWizardPlugin::loadTemplate(const QString& fileName)
{
model()->loadTemplateFile(fileName);
}
void AppWizardPlugin::reload()
{
model()->refresh();
}
#include "appwizardplugin.moc"
diff --git a/plugins/bazaar/bazaarplugin.cpp b/plugins/bazaar/bazaarplugin.cpp
index ed23c2fbd2..31853d928a 100644
--- a/plugins/bazaar/bazaarplugin.cpp
+++ b/plugins/bazaar/bazaarplugin.cpp
@@ -1,339 +1,336 @@
/***************************************************************************
* Copyright 2013-2014 Maciej Poleski *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License or (at your option) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "bazaarplugin.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bazaarutils.h"
#include "bzrannotatejob.h"
#include "copyjob.h"
#include "diffjob.h"
using namespace KDevelop;
BazaarPlugin::BazaarPlugin(QObject* parent, const QVariantList& args) :
IPlugin(QStringLiteral("kdevbazaar"), parent),
m_vcsPluginHelper(new KDevelop::VcsPluginHelper(this, this))
{
Q_UNUSED(args); // What is this?
if (QStandardPaths::findExecutable(QStringLiteral("bzr")).isEmpty()) {
setErrorDescription(i18n("Unable to find Bazaar (bzr) executable Is it installed on the system?"));
return;
}
- KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl)
- KDEV_USE_EXTENSION_INTERFACE(KDevelop::IDistributedVersionControl)
-
setObjectName(QStringLiteral("Bazaar"));
}
BazaarPlugin::~BazaarPlugin()
{
}
QString BazaarPlugin::name() const
{
return QStringLiteral("Bazaar");
}
VcsJob* BazaarPlugin::add(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::Add);
*job << "bzr" << "add";
if(recursion == NonRecursive)
*job << "--no-recurse";
*job << localLocations;
return job;
}
VcsJob* BazaarPlugin::annotate(const QUrl& localLocation, const VcsRevision& rev)
{
VcsJob* job = new BzrAnnotateJob(BazaarUtils::workingCopy(localLocation), BazaarUtils::getRevisionSpec(rev), localLocation, this, KDevelop::OutputJob::Silent);
return job;
}
VcsJob* BazaarPlugin::commit(const QString& message, const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
QDir dir = BazaarUtils::workingCopy(localLocations[0]);
DVcsJob* job = new DVcsJob(dir, this);
job->setType(VcsJob::Commit);
*job << "bzr" << "commit" << BazaarUtils::handleRecursion(localLocations, recursion) << "-m" << message;
return job;
}
VcsJob* BazaarPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn)
{
return new CopyJob(localLocationSrc, localLocationDstn, this);
}
VcsImportMetadataWidget* BazaarPlugin::createImportMetadataWidget(QWidget* parent)
{
return new DvcsImportMetadataWidget(parent);
}
VcsJob* BazaarPlugin::createWorkingCopy(const VcsLocation& sourceRepository, const QUrl& destinationDirectory, IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
// What is the purpose of recursion parameter?
DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(sourceRepository.localUrl()), this);
job->setType(VcsJob::Import);
*job << "bzr" << "branch" << sourceRepository.localUrl().url() << destinationDirectory;
return job;
}
VcsJob* BazaarPlugin::diff(const QUrl& fileOrDirectory, const VcsRevision& srcRevision, const VcsRevision& dstRevision, VcsDiff::Type, IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
VcsJob* job = new DiffJob(BazaarUtils::workingCopy(fileOrDirectory), BazaarUtils::getRevisionSpecRange(srcRevision, dstRevision), fileOrDirectory, this);
return job;
}
VcsJob* BazaarPlugin::init(const QUrl& localRepositoryRoot)
{
DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(localRepositoryRoot), this);
job->setType(VcsJob::Import);
*job << "bzr" << "init";
return job;
}
bool BazaarPlugin::isVersionControlled(const QUrl& localLocation)
{
QDir workCopy = BazaarUtils::workingCopy(localLocation);
DVcsJob* job = new DVcsJob(workCopy, this, OutputJob::Silent);
job->setType(VcsJob::Unknown);
job->setIgnoreError(true);
*job << "bzr" << "ls" << "--from-root" << "-R" << "-V";
job->exec();
if (job->status() == VcsJob::JobSucceeded) {
QList filesAndDirectoriesList;
foreach (const QString& fod, job->output().split('\n')) {
filesAndDirectoriesList.append(QFileInfo(workCopy.absolutePath() + QDir::separator() + fod));
}
QFileInfo fi(localLocation.toLocalFile());
if (fi.isDir() || fi.isFile()) {
QFileInfo file(localLocation.toLocalFile());
return filesAndDirectoriesList.contains(file);
}
}
return false;
}
VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, long unsigned int limit)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
job->setType(VcsJob::Log);
*job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(rev) << "-l" << QString::number(limit);
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog);
return job;
}
VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, const VcsRevision& limit)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
job->setType(VcsJob::Log);
*job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(limit, rev);
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog);
return job;
}
void BazaarPlugin::parseBzrLog(DVcsJob* job)
{
QVariantList result;
auto parts = job->output().split(QStringLiteral("------------------------------------------------------------"), QString::SkipEmptyParts);
foreach (const QString& part, parts) {
auto event = BazaarUtils::parseBzrLogPart(part);
if (event.revision().revisionType() != VcsRevision::Invalid)
result.append(QVariant::fromValue(event));
}
job->setResults(result);
}
VcsJob* BazaarPlugin::move(const QUrl& localLocationSrc, const QUrl& localLocationDst)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocationSrc), this);
job->setType(VcsJob::JobType::Move);
*job << "bzr" << "move" << localLocationSrc << localLocationDst;
return job;
}
VcsJob* BazaarPlugin::pull(const VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation)
{
// API describes hg pull which is git fetch equivalent
// bzr has pull, but it succeeds only if fast-forward is possible
// in other cases bzr merge should be used instead (bzr pull would fail)
// Information about repository must be provided at least once.
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this);
job->setType(VcsJob::JobType::Pull);
*job << "bzr" << "pull";
if (!localOrRepoLocationSrc.localUrl().isEmpty()) {
*job << localOrRepoLocationSrc.localUrl();
}
// localUrl always makes sense. Even on remote repositores which are handled
// transparently.
return job;
}
VcsJob* BazaarPlugin::push(const QUrl& localRepositoryLocation, const VcsLocation& localOrRepoLocationDst)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this);
job->setType(VcsJob::JobType::Push);
*job << "bzr" << "push" << localOrRepoLocationDst.localUrl();
// localUrl always makes sense. Even on remote repositores which are handled
// transparently.
return job;
}
VcsJob* BazaarPlugin::remove(const QList& localLocations)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::JobType::Remove);
*job << "bzr" << "remove" << localLocations;
return job;
}
VcsJob* BazaarPlugin::repositoryLocation(const QUrl& localLocation)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
job->setType(VcsJob::JobType::Unknown);
*job << "bzr" << "root" << localLocation; // It is only to make sure
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrRoot);
return job;
}
void BazaarPlugin::parseBzrRoot(DVcsJob* job)
{
QString filename = job->dvcsCommand().at(2);
QString rootDirectory = job->output();
QString localFilename = QFileInfo(QUrl::fromLocalFile(filename).toLocalFile()).absoluteFilePath();
QString result = localFilename.mid(localFilename.indexOf(rootDirectory) + rootDirectory.length());
job->setResults(QVariant::fromValue(result));
}
VcsJob* BazaarPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
return add(localLocations, recursion);
// How to provide "a conflict solving dialog to the user"?
// In any case this plugin is unable to make any conflict.
}
VcsJob* BazaarPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::JobType::Revert);
*job << "bzr" << "revert" << BazaarUtils::handleRecursion(localLocations, recursion);
return job;
}
VcsJob* BazaarPlugin::status(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::Status);
*job << "bzr" << "status" << "--short" << "--no-pending" << "--no-classify" << localLocations;
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrStatus);
return job;
}
void BazaarPlugin::parseBzrStatus(DVcsJob* job)
{
QVariantList result;
QSet filesWithStatus;
QDir workingCopy = job->directory();
foreach (const QString& line, job->output().split('\n')) {
auto status = BazaarUtils::parseVcsStatusInfoLine(line);
result.append(QVariant::fromValue(status));
filesWithStatus.insert(BazaarUtils::concatenatePath(workingCopy, status.url()));
}
QStringList command = job->dvcsCommand();
for (auto it = command.constBegin() + command.indexOf(QStringLiteral("--no-classify")) + 1, itEnd = command.constEnd(); it != itEnd; ++it) {
QString path = QFileInfo(*it).absoluteFilePath();
if (!filesWithStatus.contains(path)) {
filesWithStatus.insert(path);
KDevelop::VcsStatusInfo status;
status.setState(VcsStatusInfo::ItemUpToDate);
status.setUrl(QUrl::fromLocalFile(*it));
result.append(QVariant::fromValue(status));
}
}
job->setResults(result);
}
VcsJob* BazaarPlugin::update(const QList& localLocations, const VcsRevision& rev, IBasicVersionControl::RecursionMode recursion)
{
// bzr update is stronger than API (it's effectively merge)
// the best approximation is bzr pull
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
Q_UNUSED(recursion);
// recursion and file locations are ignored - we can update only whole
// working copy
job->setType(VcsJob::JobType::Update);
*job << "bzr" << "pull" << BazaarUtils::getRevisionSpec(rev);
return job;
}
VcsLocationWidget* BazaarPlugin::vcsLocation(QWidget* parent) const
{
return new KDevelop::StandardVcsLocationWidget(parent);
}
ContextMenuExtension BazaarPlugin::contextMenuExtension(Context* context)
{
m_vcsPluginHelper->setupFromContext(context);
QList const& ctxUrlList = m_vcsPluginHelper->contextUrlList();
bool isWorkingDirectory = false;
for (const QUrl & url : ctxUrlList) {
if (BazaarUtils::isValidDirectory(url)) {
isWorkingDirectory = true;
break;
}
}
if (!isWorkingDirectory) { // Not part of a repository
return ContextMenuExtension();
}
QMenu* menu = m_vcsPluginHelper->commonActions();
ContextMenuExtension menuExt;
menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction());
return menuExt;
}
diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp
index bfbd880f7a..4ded5c16a4 100644
--- a/plugins/contextbrowser/contextbrowser.cpp
+++ b/plugins/contextbrowser/contextbrowser.cpp
@@ -1,1497 +1,1495 @@
/*
* This file is part of KDevelop
*
* Copyright 2007 David Nolden
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "contextbrowser.h"
#include "contextbrowserview.h"
#include "browsemanager.h"
#include "debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser")
using KTextEditor::Attribute;
using KTextEditor::View;
// Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent.
QWidget* masterWidget(QWidget* w)
{
while(w && w->parent() && qobject_cast(w->parent()))
w = qobject_cast(w->parent());
return w;
}
namespace {
const unsigned int highlightingTimeout = 150;
const float highlightingZDepth = -5000;
const int maxHistoryLength = 30;
// Helper that determines the context to use for highlighting at a specific position
DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext)
{
DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position));
while(ctx && ctx->parentContext()
&& (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper
|| ctx->localScopeIdentifier().isEmpty()))
{
ctx = ctx->parentContext();
}
return ctx;
}
///Duchain must be locked
DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor)
{
TopDUContext* topContext = DUChainUtils::standardContextForUrl(url);
if (!topContext) return nullptr;
return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext);
}
DeclarationPointer cursorDeclaration()
{
KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
if (!view) {
return DeclarationPointer();
}
DUChainReadLocker lock;
Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration);
return DeclarationPointer(decl);
}
}
class ContextBrowserViewFactory: public KDevelop::IToolViewFactory
{
public:
ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {}
QWidget* create(QWidget *parent = nullptr) override
{
ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent);
return ret;
}
Qt::DockWidgetArea defaultPosition() override
{
return Qt::BottomDockWidgetArea;
}
QString id() const override
{
return QStringLiteral("org.kdevelop.ContextBrowser");
}
private:
ContextBrowserPlugin *m_plugin;
};
KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window )
{
m_browseManager = new BrowseManager(this);
KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window);
connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed);
m_previousButton = new QToolButton();
m_previousButton->setToolTip(i18n("Go back in context history"));
m_previousButton->setPopupMode(QToolButton::MenuButtonPopup);
m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
m_previousButton->setEnabled(false);
m_previousButton->setFocusPolicy(Qt::NoFocus);
m_previousMenu = new QMenu();
m_previousButton->setMenu(m_previousMenu);
connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious);
connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow);
m_nextButton = new QToolButton();
m_nextButton->setToolTip(i18n("Go forward in context history"));
m_nextButton->setPopupMode(QToolButton::MenuButtonPopup);
m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
m_nextButton->setEnabled(false);
m_nextButton->setFocusPolicy(Qt::NoFocus);
m_nextMenu = new QMenu();
m_nextButton->setMenu(m_nextMenu);
connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext);
connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow);
IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen"));
if(quickOpen) {
m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline);
m_outlineLine->setDefaultText(i18n("Outline..."));
m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse."));
}
connect(m_browseManager, &BrowseManager::startDelayedBrowsing,
this, &ContextBrowserPlugin::startDelayedBrowsing);
connect(m_browseManager, &BrowseManager::stopDelayedBrowsing,
this, &ContextBrowserPlugin::stopDelayedBrowsing);
connect(m_browseManager, &BrowseManager::invokeAction,
this, &ContextBrowserPlugin::invokeAction);
m_toolbarWidget = toolbarWidgetForMainWindow(window);
m_toolbarWidgetLayout = new QHBoxLayout;
m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize);
m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_toolbarWidgetLayout->setMargin(0);
m_toolbarWidgetLayout->addWidget(m_previousButton);
if (m_outlineLine) {
m_toolbarWidgetLayout->addWidget(m_outlineLine);
m_outlineLine->setMaximumWidth(600);
connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear);
}
m_toolbarWidgetLayout->addWidget(m_nextButton);
if(m_toolbarWidget->children().isEmpty())
m_toolbarWidget->setLayout(m_toolbarWidgetLayout);
connect(ICore::self()->documentController(), &IDocumentController::documentActivated,
this, &ContextBrowserPlugin::documentActivated);
return ret;
}
void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile,
KActionCollection& actions)
{
xmlFile = QStringLiteral("kdevcontextbrowser.rc");
QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode"));
sourceBrowseMode->setText( i18n("Source &Browse Mode") );
sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) );
sourceBrowseMode->setCheckable(true);
connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing);
QAction* previousContext = actions.addAction(QStringLiteral("previous_context"));
previousContext->setText( i18n("&Previous Visited Context") );
previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) );
actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left );
QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut);
QAction* nextContext = actions.addAction(QStringLiteral("next_context"));
nextContext->setText( i18n("&Next Visited Context") );
nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) );
actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right );
QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut);
QAction* previousUse = actions.addAction(QStringLiteral("previous_use"));
previousUse->setText( i18n("&Previous Use") );
previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) );
actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left );
QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut);
QAction* nextUse = actions.addAction(QStringLiteral("next_use"));
nextUse->setText( i18n("&Next Use") );
nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) );
actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right );
QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut);
QWidgetAction* outline = new QWidgetAction(this);
outline->setText(i18n("Context Browser"));
QWidget* w = toolbarWidgetForMainWindow(window);
w->setHidden(false);
outline->setDefaultWidget(w);
actions.addAction(QStringLiteral("outline_line"), outline);
// Add to the actioncollection so one can set global shortcuts for the action
actions.addAction(QStringLiteral("find_uses"), m_findUses);
}
void ContextBrowserPlugin::nextContextShortcut()
{
// TODO: cleanup
historyNext();
}
void ContextBrowserPlugin::previousContextShortcut()
{
// TODO: cleanup
historyPrevious();
}
K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();)
ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&)
: KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent)
, m_viewFactory(new ContextBrowserViewFactory(this))
, m_nextHistoryIndex(0)
, m_textHintProvider(this)
{
- KDEV_USE_EXTENSION_INTERFACE( IContextBrowser )
-
core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory);
connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated );
connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady);
connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged );
connect( DUChain::self(), &DUChain::declarationSelected,
this, &ContextBrowserPlugin::declarationSelectedInUI );
m_updateTimer = new QTimer(this);
m_updateTimer->setSingleShot(true);
connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews );
//Needed global action for the context-menu extensions
m_findUses = new QAction(i18n("Find Uses"), this);
connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses);
}
ContextBrowserPlugin::~ContextBrowserPlugin()
{
///TODO: QObject inheritance should suffice?
delete m_nextMenu;
delete m_previousMenu;
delete m_toolbarWidgetLayout;
delete m_previousButton;
delete m_outlineLine;
delete m_nextButton;
}
void ContextBrowserPlugin::unload()
{
core()->uiController()->removeToolView(m_viewFactory);
}
KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context)
{
KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context );
KDevelop::DeclarationContext *codeContext = dynamic_cast(context);
if (!codeContext)
return menuExt;
DUChainReadLocker lock(DUChain::lock());
if(!codeContext->declaration().data())
return menuExt;
qRegisterMetaType("KDevelop::IndexedDeclaration");
menuExt.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_findUses);
return menuExt;
}
void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration)
{
QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection,
Q_ARG(KDevelop::DeclarationPointer, declaration));
}
void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration)
{
DUChainReadLocker lock;
Declaration* decl = declaration.data();
if(!decl) {
return;
}
QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise);
if(!toolView) {
return;
}
ContextBrowserView* view = dynamic_cast(toolView);
Q_ASSERT(view);
view->allowLockedUpdate();
view->setDeclaration(decl, decl->topContext(), true);
//We may get deleted while the call to acceptLink, so make sure we don't crash in that case
QPointer widget = dynamic_cast(view->navigationWidget());
if(widget && widget->context()) {
NavigationContextPointer nextContext = widget->context()->execute(
NavigationAction(declaration, KDevelop::NavigationAction::ShowUses));
if(widget) {
widget->setContext( nextContext );
}
}
}
void ContextBrowserPlugin::findUses()
{
showUses(cursorDeclaration());
}
ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin)
: m_plugin(plugin)
{
}
QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor)
{
m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor);
if(!view) {
qWarning() << "could not cast to view";
}else{
m_plugin->m_mouseHoverDocument = view->document()->url();
m_plugin->m_updateViews << view;
}
m_plugin->m_updateTimer->start(1); // triggers updateViews()
m_plugin->showToolTip(view, cursor);
return QString();
}
void ContextBrowserPlugin::stopDelayedBrowsing() {
hideToolTip();
}
void ContextBrowserPlugin::invokeAction(int index)
{
if (!m_currentNavigationWidget)
return;
auto navigationWidget = qobject_cast(m_currentNavigationWidget);
if (!navigationWidget)
return;
// TODO: Add API in AbstractNavigation{Widget,Context}?
QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index));
}
void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) {
if(!m_currentToolTip) {
showToolTip(view, view->cursorPosition());
}
}
void ContextBrowserPlugin::hideToolTip() {
if(m_currentToolTip) {
m_currentToolTip->deleteLater();
m_currentToolTip = nullptr;
m_currentNavigationWidget = nullptr;
m_currentToolTipProblems.clear();
m_currentToolTipDeclaration = {};
}
}
static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position)
{
QVector problems;
auto modelsData = ICore::self()->languageController()->problemModelSet()->models();
foreach (auto modelData, modelsData) {
foreach (auto problem, modelData.model->problems(topContext->url())) {
DocumentRange problemRange = problem->finalLocation();
if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position)))
problems += problem;
}
}
return problems;
}
static QVector findProblemsCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view)
{
QVector allProblems;
auto modelsData = ICore::self()->languageController()->problemModelSet()->models();
foreach (auto modelData, modelsData) {
foreach (auto problem, modelData.model->problems(topContext->url())) {
allProblems += problem;
}
}
if (allProblems.isEmpty())
return allProblems;
std::sort(allProblems.begin(), allProblems.end(),
[position](const KDevelop::IProblem::Ptr a, const KDevelop::IProblem::Ptr b) {
const auto aRange = a->finalLocation();
const auto bRange = b->finalLocation();
const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()),
qAbs(aRange.end().line() - position.line()));
const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()),
qAbs(bRange.end().line() - position.line()));
if (aLineDistance != bLineDistance) {
return aLineDistance < bLineDistance;
}
if (aRange.start().line() == bRange.start().line()) {
return qAbs(aRange.start().column() - position.column()) <
qAbs(bRange.start().column() - position.column());
}
return qAbs(aRange.end().column() - position.column()) <
qAbs(bRange.end().column() - position.column());
});
QVector closestProblems;
// Show problems, located on the same line
foreach (auto problem, allProblems) {
auto r = problem->finalLocation();
if (r.onSingleLine() && r.start().line() == position.line())
closestProblems += problem;
else
break;
}
// If not, only show it in case there's only whitespace
// between the current cursor position and the problem line
if (closestProblems.isEmpty()) {
foreach (auto problem, allProblems) {
auto r = problem->finalLocation();
KTextEditor::Range dist;
KTextEditor::Cursor bound(r.start().line(), 0);
if (position < r.start())
dist = KTextEditor::Range(position, bound);
else {
bound.setLine(r.end().line() + 1);
dist = KTextEditor::Range(bound, position);
}
if (view->document()->text(dist).trimmed().isEmpty())
closestProblems += problem;
else
break;
}
}
return closestProblems;
}
QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position)
{
QUrl viewUrl = view->document()->url();
auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl);
DUChainReadLocker lock(DUChain::lock());
foreach (const auto language, languages) {
auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position));
auto navigationWidget = qobject_cast(widget);
if(navigationWidget)
return navigationWidget;
}
TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url());
if (topContext) {
// first pass: find problems under the cursor
const auto problems = findProblemsUnderCursor(topContext, position);
if (!problems.isEmpty()) {
if (problems == m_currentToolTipProblems && m_currentToolTip) {
return nullptr;
}
m_currentToolTipProblems = problems;
auto widget = new AbstractNavigationWidget;
auto context = new ProblemNavigationContext(problems);
context->setTopContext(TopDUContextPointer(topContext));
widget->setContext(NavigationContextPointer(context));
return widget;
}
}
auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration;
Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor);
if (decl && decl->kind() == Declaration::Alias) {
AliasDeclaration* alias = dynamic_cast(decl);
Q_ASSERT(alias);
DUChainReadLocker lock;
decl = alias->aliasedDeclaration().declaration();
}
if(decl) {
if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip)
return nullptr;
m_currentToolTipDeclaration = IndexedDeclaration(decl);
return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl));
}
if (topContext) {
// second pass: find closest problem to the cursor
const auto problems = findProblemsCloseToCursor(topContext, position, view);
if (!problems.isEmpty()) {
if (problems == m_currentToolTipProblems && m_currentToolTip) {
return nullptr;
}
m_currentToolTipProblems = problems;
auto widget = new AbstractNavigationWidget;
// since the problem is not under cursor: show location
widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext::ShowLocation)));
return widget;
}
}
return nullptr;
}
void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) {
ContextBrowserView* contextView = browserViewForWidget(view);
if(contextView && contextView->isVisible() && !contextView->isLocked())
return; // If the context-browser view is visible, it will care about updating by itself
auto navigationWidget = navigationWidgetForPosition(view, position);
if(navigationWidget) {
// If we have an invisible context-view, assign the tooltip navigation-widget to it.
// If the user makes the context-view visible, it will instantly contain the correct widget.
if(contextView && !contextView->isLocked())
contextView->setNavigationWidget(navigationWidget);
if(m_currentToolTip) {
m_currentToolTip->deleteLater();
m_currentToolTip = nullptr;
m_currentNavigationWidget = nullptr;
}
KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget);
KTextEditor::Range itemRange;
{
DUChainReadLocker lock;
auto viewUrl = view->document()->url();
itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range;
}
tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange));
tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) );
QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged,
this, &ContextBrowserPlugin::hideToolTip );
QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged,
this, &ContextBrowserPlugin::hideToolTip );
qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size();
m_currentToolTip = tooltip;
m_currentNavigationWidget = navigationWidget;
ActiveToolTip::showToolTip(tooltip);
if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) {
connect(view, &View::cursorPositionChanged,
this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection);
} else {
disconnect(view, &View::cursorPositionChanged,
this, &ContextBrowserPlugin::hideToolTip);
}
}else{
qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget";
}
}
void ContextBrowserPlugin::clearMouseHover() {
m_mouseHoverCursor = KTextEditor::Cursor::invalid();
m_mouseHoverDocument.clear();
}
Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const {
if( !m_highlightAttribute ) {
m_highlightAttribute = Attribute::Ptr( new Attribute() );
m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal);
m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground());
m_highlightAttribute->setBackgroundFillWhitespace(true);
auto iface = qobject_cast(view);
auto background = iface->configValue(QStringLiteral("search-highlight-color")).value();
m_highlightAttribute->setBackground(background);
}
return m_highlightAttribute;
}
void ContextBrowserPlugin::colorSetupChanged() {
m_highlightAttribute = Attribute::Ptr();
}
Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const {
return highlightedUseAttribute(view);
}
void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) {
if( !view || !decl ) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration";
return;
}
ViewHighlights& highlights(m_highlightedRanges[view]);
KDevelop::DUChainReadLocker lock;
// Highlight the declaration
highlights.highlights << decl->createRangeMoving();
highlights.highlights.back()->setAttribute(highlightedUseAttribute(view));
highlights.highlights.back()->setZDepth(highlightingZDepth);
// Highlight uses
{
QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision();
for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt)
{
for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt)
{
highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key()));
highlights.highlights.back()->setAttribute(highlightedUseAttribute(view));
highlights.highlights.back()->setZDepth(highlightingZDepth);
}
}
}
if( FunctionDefinition* def = FunctionDefinition::definition(decl) )
{
highlights.highlights << def->createRangeMoving();
highlights.highlights.back()->setAttribute(highlightedUseAttribute(view));
highlights.highlights.back()->setZDepth(highlightingZDepth);
}
}
Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight)
{
Q_UNUSED(mouseHighlight);
Declaration* foundDeclaration = nullptr;
if(m_useDeclaration.data()) {
foundDeclaration = m_useDeclaration.data();
}else{
//If we haven't found a special language object, search for a use/declaration and eventually highlight it
foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration );
if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) {
AliasDeclaration* alias = dynamic_cast(foundDeclaration);
Q_ASSERT(alias);
DUChainReadLocker lock;
foundDeclaration = alias->aliasedDeclaration().declaration();
}
}
return foundDeclaration;
}
ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget)
{
foreach(ContextBrowserView* contextView, m_views) {
if(masterWidget(contextView) == masterWidget(widget)) {
return contextView;
}
}
return nullptr;
}
void ContextBrowserPlugin::updateForView(View* view)
{
bool allowHighlight = true;
if(view->selection())
{
// If something is selected, we unhighlight everything, so that we don't conflict with the
// kate plugin that highlights occurrences of the selected string, and also to reduce the
// overall amount of concurrent highlighting.
allowHighlight = false;
}
if(m_highlightedRanges[view].keep)
{
m_highlightedRanges[view].keep = false;
return;
}
// Clear all highlighting
m_highlightedRanges.clear();
// Re-highlight
ViewHighlights& highlights = m_highlightedRanges[view];
QUrl url = view->document()->url();
IDocument* activeDoc = core()->documentController()->activeDocument();
bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid());
bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document()));
KTextEditor::Cursor highlightPosition;
if (mouseHighlight)
highlightPosition = m_mouseHoverCursor;
else
highlightPosition = KTextEditor::Cursor(view->cursorPosition());
///Pick a language
ILanguageSupport* language = nullptr;
if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url;
return;
}else{
language = ICore::self()->languageController()->languagesForUrl(url).front();
}
///Check whether there is a special language object to highlight (for example a macro)
KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition);
ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr;
if(specialRange.isValid())
{
// Highlight a special language object
if(allowHighlight)
{
highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url)));
highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view));
highlights.highlights.back()->setZDepth(highlightingZDepth);
}
if(updateBrowserView)
updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition));
}else{
KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 );
if(!lock.locked()) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time";
return;
}
TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url());
if (!topContext)
return;
DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext);
if (!ctx)
return;
//Only update the history if this context is around the text cursor
if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument())
{
updateHistory(ctx, highlightPosition);
}
Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight);
if( foundDeclaration ) {
m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration);
if(allowHighlight)
addHighlight( view, foundDeclaration );
if(updateBrowserView)
updateBrowserView->setDeclaration(foundDeclaration, topContext);
}else{
if(updateBrowserView)
updateBrowserView->setContext(ctx);
}
}
}
void ContextBrowserPlugin::updateViews()
{
foreach( View* view, m_updateViews ) {
updateForView(view);
}
m_updateViews.clear();
m_useDeclaration = IndexedDeclaration();
}
void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl)
{
m_useDeclaration = IndexedDeclaration(decl.data());
KTextEditor::View* view = core()->documentController()->activeTextDocumentView();
if(view)
m_updateViews << view;
if(!m_updateViews.isEmpty())
m_updateTimer->start(highlightingTimeout); // triggers updateViews()
}
void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/)
{
const auto url = file.toUrl();
for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) {
if(it.key()->document()->url() == url) {
if(!m_updateViews.contains(it.key())) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update";
m_updateViews << it.key();
// Don't change the highlighted declaration after finished parse-jobs
(*it).keep = true;
}
}
}
if(!m_updateViews.isEmpty())
m_updateTimer->start(highlightingTimeout);
}
void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document )
{
Q_ASSERT(document->textDocument());
connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated );
foreach( View* view, document->textDocument()->views() )
viewCreated( document->textDocument(), view );
}
void ContextBrowserPlugin::documentActivated( IDocument* doc )
{
if (m_outlineLine)
m_outlineLine->clear();
if (View* view = doc->activeTextView())
{
cursorPositionChanged(view, view->cursorPosition());
}
}
void ContextBrowserPlugin::viewDestroyed( QObject* obj )
{
m_highlightedRanges.remove(static_cast(obj));
m_updateViews.remove(static_cast(obj));
}
void ContextBrowserPlugin::selectionChanged( View* view )
{
clearMouseHover();
m_updateViews.insert(view);
m_updateTimer->start(highlightingTimeout/2); // triggers updateViews()
}
void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition )
{
if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos)
{
//Do not update the highlighting while typing
m_lastInsertionDocument = nullptr;
m_lastInsertionPos = KTextEditor::Cursor();
if(m_highlightedRanges.contains(view))
m_highlightedRanges[view].keep = true;
}else{
if(m_highlightedRanges.contains(view))
m_highlightedRanges[view].keep = false;
}
clearMouseHover();
m_updateViews.insert(view);
m_updateTimer->start(highlightingTimeout/2); // triggers updateViews()
}
void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text)
{
m_lastInsertionDocument = doc;
m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size());
}
void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v )
{
disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen
connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged );
connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed );
disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted);
connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted);
disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged);
KTextEditor::TextHintInterface *iface = dynamic_cast(v);
if( !iface )
return;
iface->setTextHintDelay(highlightingTimeout);
iface->registerTextHintProvider(&m_textHintProvider);
}
void ContextBrowserPlugin::registerToolView(ContextBrowserView* view)
{
m_views << view;
}
void ContextBrowserPlugin::previousUseShortcut()
{
switchUse(false);
}
void ContextBrowserPlugin::nextUseShortcut()
{
switchUse(true);
}
KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) {
return KTextEditor::Range(cursor, cursor);
}
void ContextBrowserPlugin::switchUse(bool forward)
{
View* view = core()->documentController()->activeTextDocumentView();
if(view) {
KTextEditor::Document* doc = view->document();
KDevelop::DUChainReadLocker lock( DUChain::lock() );
KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url());
if( chosen )
{
KTextEditor::Cursor cCurrent(view->cursorPosition());
KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent);
Declaration* decl = nullptr;
//If we have a locked declaration, use that for jumping
foreach(ContextBrowserView* view, m_views) {
decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple
if(decl)
break;
}
if(!decl) //Try finding a declaration under the cursor
decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration;
if (decl && decl->kind() == Declaration::Alias) {
AliasDeclaration* alias = dynamic_cast(decl);
Q_ASSERT(alias);
DUChainReadLocker lock;
decl = alias->aliasedDeclaration().declaration();
}
if(decl) {
Declaration* target = nullptr;
if(forward)
//Try jumping from definition to declaration
target = DUChainUtils::declarationForDefinition(decl, chosen);
else if(decl->url().toUrl() == doc->url() && decl->range().contains(c))
//Try jumping from declaration to definition
target = FunctionDefinition::definition(decl);
if(target && target != decl) {
KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start();
QUrl document = target->url().toUrl();
lock.unlock();
core()->documentController()->openDocument( document, cursorToRange(jumpTo) );
return;
}else{
//Always work with the declaration instead of the definition
decl = DUChainUtils::declarationForDefinition(decl, chosen);
}
}
if(!decl) {
//Pick the last use we have highlighted
decl = m_lastHighlightedDeclaration.data();
}
if(decl) {
KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id());
if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1)
usingFiles.insert(0, decl->topContext());
if(decl->range().contains(c) && decl->url() == chosen->url()) {
//The cursor is directly on the declaration. Jump to the first or last use.
if(!usingFiles.isEmpty()) {
TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data();
if(top) {
QList useRanges = allUses(top, decl, true);
std::sort(useRanges.begin(), useRanges.end());
if(!useRanges.isEmpty()) {
QUrl url = top->url().toUrl();
KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back());
lock.unlock();
core()->documentController()->openDocument(url, cursorToRange(selectUse.start()));
}
}
}
return;
}
//Check whether we are within a use
QList localUses = allUses(chosen, decl, true);
std::sort(localUses.begin(), localUses.end());
for(int a = 0; a < localUses.size(); ++a) {
int nextUse = (forward ? a+1 : a-1);
bool pick = localUses[a].contains(c);
if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) {
//Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use
pick = true;
}
if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) {
//Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one
pick = true;
}
if(!pick && a == 0 && c < localUses[a].start) {
if(!forward) {
//Will automatically jump to previous file
}else{
nextUse = 0; //We are before the first use, so jump to it.
}
pick = true;
}
if(!pick && a == localUses.size()-1 && c >= localUses[a].end) {
if(forward) {
//Will automatically jump to next file
}else{ //We are behind the last use, but moving backward. So pick the last use.
nextUse = a;
}
pick = true;
}
if(pick) {
//Make sure we end up behind the use
if(nextUse != a)
while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty()))
++nextUse;
//Make sure we end up before the use
if(nextUse != a)
while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty()))
--nextUse;
//Jump to the next use
qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse;
if(nextUse < 0 || nextUse == localUses.size()) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file";
//Jump to the first use in the next using top-context
int indexInFiles = usingFiles.indexOf(chosen);
if(indexInFiles != -1) {
int nextFile = (forward ? indexInFiles+1 : indexInFiles-1);
qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile;
if(nextFile < 0 || nextFile >= usingFiles.size()) {
//Open the declaration, or the definition
if(nextFile >= usingFiles.size()) {
Declaration* definition = FunctionDefinition::definition(decl);
if(definition)
decl = definition;
}
QUrl u = decl->url().toUrl();
KTextEditor::Range range = decl->rangeInCurrentRevision();
range.setEnd(range.start());
lock.unlock();
core()->documentController()->openDocument(u, range);
return;
}else{
TopDUContext* nextTop = usingFiles[nextFile].data();
QUrl u = nextTop->url().toUrl();
QList nextTopUses = allUses(nextTop, decl, true);
std::sort(nextTopUses.begin(), nextTopUses.end());
if(!nextTopUses.isEmpty()) {
KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back());
range.setEnd(range.start());
lock.unlock();
core()->documentController()->openDocument(u, range);
}
return;
}
}else{
qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list";
}
}else{
QUrl url = chosen->url().toUrl();
KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]);
range.setEnd(range.start());
lock.unlock();
core()->documentController()->openDocument(url, range);
return;
}
}
}
}
}
}
}
void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view)
{
m_views.removeAll(view);
}
// history browsing
QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window )
{
//TODO: support multiple windows (if that ever gets revived)
if (!m_toolbarWidget) {
m_toolbarWidget = new QWidget(window);
}
return m_toolbarWidget;
}
void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument,
const KTextEditor::Cursor& newCursor,
KDevelop::IDocument* previousDocument,
const KTextEditor::Cursor& previousCursor) {
DUChainReadLocker lock(DUChain::lock());
/*TODO: support multiple windows if that ever gets revived
if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this))
return;
*/
if(previousDocument && previousCursor.isValid()) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source";
DUContext* context = getContextAt(previousDocument->url(), previousCursor);
if(context) {
updateHistory(context, KTextEditor::Cursor(previousCursor), true);
}else{
//We just want this place in the history
m_history.resize(m_nextHistoryIndex); // discard forward history
m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor))));
++m_nextHistoryIndex;
}
}
qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor;
if(newDocument && newCursor.isValid()) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target";
DUContext* context = getContextAt(newDocument->url(), newCursor);
if(context) {
updateHistory(context, KTextEditor::Cursor(newCursor), true);
}else{
//We just want this place in the history
m_history.resize(m_nextHistoryIndex); // discard forward history
m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor))));
++m_nextHistoryIndex;
if (m_outlineLine) m_outlineLine->clear();
}
}
}
void ContextBrowserPlugin::updateButtonState()
{
m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() );
m_previousButton->setEnabled( m_nextHistoryIndex >= 2 );
}
void ContextBrowserPlugin::historyNext() {
if(m_nextHistoryIndex >= m_history.size()) {
return;
}
openDocument(m_nextHistoryIndex); // opening the document at given position
// will update the widget for us
++m_nextHistoryIndex;
updateButtonState();
}
void ContextBrowserPlugin::openDocument(int historyIndex) {
Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index");
Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range");
DocumentCursor c = m_history[historyIndex].computePosition();
if (c.isValid() && !c.document.str().isEmpty()) {
disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed);
ICore::self()->documentController()->openDocument(c.document.toUrl(), c);
connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed);
KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() );
updateDeclarationListBox(m_history[historyIndex].context.data());
}
}
void ContextBrowserPlugin::historyPrevious() {
if(m_nextHistoryIndex < 2) {
return;
}
--m_nextHistoryIndex;
openDocument(m_nextHistoryIndex-1); // opening the document at given position
// will update the widget for us
updateButtonState();
}
QString ContextBrowserPlugin::actionTextFor(int historyIndex) const
{
const HistoryEntry& entry = m_history.at(historyIndex);
QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString();
if(actionText.isEmpty())
actionText = entry.alternativeString;
if(actionText.isEmpty())
actionText = QStringLiteral("");
actionText += QLatin1String(" @ ");
QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName();
actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1);
return actionText;
}
/*
inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he)
{
DocumentCursor c = he.computePosition();
debug << "\n\tHistoryEntry " << c.line << " " << c.document.str();
return debug;
}
*/
void ContextBrowserPlugin::nextMenuAboutToShow() {
QList indices;
for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) {
indices << a;
}
fillHistoryPopup(m_nextMenu, indices);
}
void ContextBrowserPlugin::previousMenuAboutToShow() {
QList indices;
for(int a = m_nextHistoryIndex-2; a >= 0; --a) {
indices << a;
}
fillHistoryPopup(m_previousMenu, indices);
}
void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) {
menu->clear();
KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() );
foreach(int index, historyIndices) {
QAction* action = new QAction(actionTextFor(index), menu);
action->setData(index);
menu->addAction(action);
connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered);
}
}
bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context,
const KTextEditor::Cursor& /*position*/) const
{
if (m_nextHistoryIndex == 0) return false;
Q_ASSERT(m_nextHistoryIndex <= m_history.count());
const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1);
KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary??
Q_ASSERT(context);
return IndexedDUContext(context) == he.context;
}
void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force)
{
qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history";
if(m_outlineLine && m_outlineLine->isVisible())
updateDeclarationListBox(context);
if(!context || (!context->owner() && !force)) {
return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes
//This keeps the history cleaner
}
if (isPreviousEntry(context, position)) {
if(m_nextHistoryIndex) {
HistoryEntry& he = m_history[m_nextHistoryIndex-1];
he.setCursorPosition(position);
}
return;
} else { // Append new history entry
m_history.resize(m_nextHistoryIndex); // discard forward history
m_history.append(HistoryEntry(IndexedDUContext(context), position));
++m_nextHistoryIndex;
updateButtonState();
if(m_history.size() > (maxHistoryLength + 5)) {
m_history = m_history.mid(m_history.size() - maxHistoryLength);
m_nextHistoryIndex = m_history.size();
}
}
}
void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) {
if(!context || !context->owner()) {
qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box";
m_listUrl = IndexedString(); ///@todo Compute the context in the document here
if (m_outlineLine) m_outlineLine->clear();
return;
}
Declaration* decl = context->owner();
m_listUrl = context->url();
Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext());
FunctionType::Ptr function = specialDecl->type();
QString text = specialDecl->qualifiedIdentifier().toString();
if(function)
text += function->partToString(KDevelop::FunctionType::SignatureArguments);
if(m_outlineLine && !m_outlineLine->hasFocus())
{
m_outlineLine->setText(text);
m_outlineLine->setCursorPosition(0);
}
qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text;
}
void ContextBrowserPlugin::actionTriggered() {
QAction* action = qobject_cast(sender());
Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int);
int historyPosition = action->data().toInt();
// qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history;
if(historyPosition >= 0 && historyPosition < m_history.size()) {
m_nextHistoryIndex = historyPosition + 1;
openDocument(historyPosition);
updateButtonState();
}
}
void ContextBrowserPlugin::doNavigate(NavigationActionType action)
{
KTextEditor::View* view = qobject_cast(sender());
if(!view) {
qWarning() << "sender is not a view";
return;
}
KTextEditor::CodeCompletionInterface* iface = qobject_cast(view);
if(!iface || iface->isCompletionActive())
return; // If code completion is active, the actions should be handled by the completion widget
QWidget* widget = m_currentNavigationWidget.data();
if(!widget || !widget->isVisible())
{
ContextBrowserView* contextView = browserViewForWidget(view);
if(contextView)
widget = contextView->navigationWidget();
}
if(widget)
{
AbstractNavigationWidget* navWidget = qobject_cast(widget);
if (navWidget)
{
switch(action) {
case Accept:
navWidget->accept();
break;
case Back:
navWidget->back();
break;
case Left:
navWidget->previous();
break;
case Right:
navWidget->next();
break;
case Up:
navWidget->up();
break;
case Down:
navWidget->down();
break;
}
}
}
}
void ContextBrowserPlugin::navigateAccept() {
doNavigate(Accept);
}
void ContextBrowserPlugin::navigateBack() {
doNavigate(Back);
}
void ContextBrowserPlugin::navigateDown() {
doNavigate(Down);
}
void ContextBrowserPlugin::navigateLeft() {
doNavigate(Left);
}
void ContextBrowserPlugin::navigateRight() {
doNavigate(Right);
}
void ContextBrowserPlugin::navigateUp() {
doNavigate(Up);
}
//BEGIN HistoryEntry
ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) {
}
ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) {
//Use a position relative to the context
setCursorPosition(cursorPosition);
if(ctx.data())
alternativeString = ctx.data()->scopeIdentifier(true).toString();
if(!alternativeString.isEmpty())
alternativeString += i18n("(changed)"); //This is used when the context was deleted in between
}
DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const {
KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() );
DocumentCursor ret;
if(context.data()) {
ret = DocumentCursor(context.data()->url(), relativeCursorPosition);
ret.setLine(ret.line() + context.data()->range().start.line);
}else{
ret = absoluteCursorPosition;
}
return ret;
}
void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) {
KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() );
if(context.data()) {
absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition);
relativeCursorPosition = cursorPosition;
relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line);
}
}
// kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on
#include "contextbrowser.moc"
diff --git a/plugins/cvs/cvsplugin.cpp b/plugins/cvs/cvsplugin.cpp
index 976aea0916..988ebc0908 100644
--- a/plugins/cvs/cvsplugin.cpp
+++ b/plugins/cvs/cvsplugin.cpp
@@ -1,489 +1,486 @@
/***************************************************************************
* Copyright 2007 Robert Gruber *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "cvsplugin.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cvsmainview.h"
#include "cvsproxy.h"
#include "cvsjob.h"
#include "editorsview.h"
#include "commitdialog.h"
#include "cvsgenericoutputview.h"
#include "checkoutdialog.h"
#include "importdialog.h"
#include "importmetadatawidget.h"
#include "debug.h"
#include
#include
#include
Q_LOGGING_CATEGORY(PLUGIN_CVS, "kdevplatform.plugins.cvs")
K_PLUGIN_FACTORY(KDevCvsFactory, registerPlugin();)
// K_EXPORT_PLUGIN(KDevCvsFactory(KAboutData("kdevcvs", "kdevcvs", ki18n("CVS"), "0.1", ki18n("Support for CVS version control system"), KAboutData::License_GPL)))
class KDevCvsViewFactory: public KDevelop::IToolViewFactory
{
public:
KDevCvsViewFactory(CvsPlugin *plugin): m_plugin(plugin) {}
QWidget* create(QWidget *parent = nullptr) override {
return new CvsMainView(m_plugin, parent);
}
Qt::DockWidgetArea defaultPosition() override {
return Qt::BottomDockWidgetArea;
}
QString id() const override {
return QStringLiteral("org.kdevelop.CVSView");
}
private:
CvsPlugin *m_plugin;
};
class CvsPluginPrivate
{
public:
explicit CvsPluginPrivate(CvsPlugin *pThis)
: m_factory(new KDevCvsViewFactory(pThis))
, m_proxy(new CvsProxy(pThis))
, m_common(new KDevelop::VcsPluginHelper(pThis, pThis)) {}
KDevCvsViewFactory* m_factory;
QPointer m_proxy;
QScopedPointer m_common;
};
CvsPlugin::CvsPlugin(QObject *parent, const QVariantList &)
: KDevelop::IPlugin(QStringLiteral("kdevcvs"), parent)
, d(new CvsPluginPrivate(this))
{
- KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl)
- KDEV_USE_EXTENSION_INTERFACE(KDevelop::ICentralizedVersionControl)
-
core()->uiController()->addToolView(i18n("CVS"), d->m_factory);
setXMLFile(QStringLiteral("kdevcvs.rc"));
setupActions();
}
CvsPlugin::~CvsPlugin()
{
}
void CvsPlugin::unload()
{
core()->uiController()->removeToolView( d->m_factory );
}
CvsProxy* CvsPlugin::proxy()
{
return d->m_proxy;
}
void CvsPlugin::setupActions()
{
QAction *action;
action = actionCollection()->addAction(QStringLiteral("cvs_import"));
action->setText(i18n("Import Directory..."));
connect(action, &QAction::triggered, this, &CvsPlugin::slotImport);
action = actionCollection()->addAction(QStringLiteral("cvs_checkout"));
action->setText(i18n("Checkout..."));
connect(action, &QAction::triggered, this, &CvsPlugin::slotCheckout);
action = actionCollection()->addAction(QStringLiteral("cvs_status"));
action->setText(i18n("Status..."));
connect(action, &QAction::triggered, this, &CvsPlugin::slotStatus);
}
const QUrl CvsPlugin::urlFocusedDocument() const
{
KParts::ReadOnlyPart *plugin =
dynamic_cast(core()->partController()->activePart());
if (plugin) {
if (plugin->url().isLocalFile()) {
return plugin->url();
}
}
return QUrl();
}
void CvsPlugin::slotImport()
{
QUrl url = urlFocusedDocument();
ImportDialog dlg(this, url);
dlg.exec();
}
void CvsPlugin::slotCheckout()
{
///@todo don't use proxy directly; use interface instead
CheckoutDialog dlg(this);
dlg.exec();
}
void CvsPlugin::slotStatus()
{
QUrl url = urlFocusedDocument();
QList urls;
urls << url;
KDevelop::VcsJob* j = status(urls, KDevelop::IBasicVersionControl::Recursive);
CvsJob* job = dynamic_cast(j);
if (job) {
CvsGenericOutputView* view = new CvsGenericOutputView(job);
emit addNewTabToMainView(view, i18n("Status"));
KDevelop::ICore::self()->runController()->registerJob(job);
}
}
KDevelop::ContextMenuExtension CvsPlugin::contextMenuExtension(KDevelop::Context* context)
{
d->m_common->setupFromContext(context);
QList const & ctxUrlList = d->m_common->contextUrlList();
bool hasVersionControlledEntries = false;
foreach(const QUrl &url, ctxUrlList) {
if (d->m_proxy->isValidDirectory(url)) {
hasVersionControlledEntries = true;
break;
}
}
qCDebug(PLUGIN_CVS) << "version controlled?" << hasVersionControlledEntries;
if (!hasVersionControlledEntries)
return IPlugin::contextMenuExtension(context);
QMenu* menu = d->m_common->commonActions();
menu->addSeparator();
QAction *action;
// Just add actions which are not covered by the cvscommon plugin
action = new QAction(i18n("Edit"), this);
connect(action, &QAction::triggered, this, &CvsPlugin::ctxEdit);
menu->addAction(action);
action = new QAction(i18n("Unedit"), this);
connect(action, &QAction::triggered, this, &CvsPlugin::ctxUnEdit);
menu->addAction(action);
action = new QAction(i18n("Show Editors"), this);
connect(action, &QAction::triggered, this, &CvsPlugin::ctxEditors);
menu->addAction(action);
KDevelop::ContextMenuExtension menuExt;
menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, menu->menuAction());
return menuExt;
}
void CvsPlugin::ctxEdit()
{
QList const & urls = d->m_common->contextUrlList();
Q_ASSERT(!urls.empty());
KDevelop::VcsJob* j = edit(urls.front());
CvsJob* job = dynamic_cast(j);
if (job) {
connect(job, &CvsJob::result,
this, &CvsPlugin::jobFinished);
KDevelop::ICore::self()->runController()->registerJob(job);
}
}
void CvsPlugin::ctxUnEdit()
{
QList const & urls = d->m_common->contextUrlList();
Q_ASSERT(!urls.empty());
KDevelop::VcsJob* j = unedit(urls.front());
CvsJob* job = dynamic_cast(j);
if (job) {
connect(job, &CvsJob::result,
this, &CvsPlugin::jobFinished);
KDevelop::ICore::self()->runController()->registerJob(job);
}
}
void CvsPlugin::ctxEditors()
{
QList const & urls = d->m_common->contextUrlList();
Q_ASSERT(!urls.empty());
CvsJob* job = d->m_proxy->editors(findWorkingDir(urls.front()),
urls);
if (job) {
KDevelop::ICore::self()->runController()->registerJob(job);
EditorsView* view = new EditorsView(job);
emit addNewTabToMainView(view, i18n("Editors"));
}
}
QString CvsPlugin::findWorkingDir(const QUrl& location)
{
QFileInfo fileInfo(location.toLocalFile());
// find out correct working directory
if (fileInfo.isFile()) {
return fileInfo.absolutePath();
} else {
return fileInfo.absoluteFilePath();
}
}
// Begin: KDevelop::IBasicVersionControl
bool CvsPlugin::isVersionControlled(const QUrl & localLocation)
{
return d->m_proxy->isVersionControlled(localLocation);
}
KDevelop::VcsJob * CvsPlugin::repositoryLocation(const QUrl & localLocation)
{
Q_UNUSED(localLocation);
return nullptr;
}
KDevelop::VcsJob * CvsPlugin::add(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
CvsJob* job = d->m_proxy->add(findWorkingDir(localLocations[0]),
localLocations,
(recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false);
return job;
}
KDevelop::VcsJob * CvsPlugin::remove(const QList & localLocations)
{
CvsJob* job = d->m_proxy->remove(findWorkingDir(localLocations[0]),
localLocations);
return job;
}
KDevelop::VcsJob * CvsPlugin::localRevision(const QUrl & localLocation, KDevelop::VcsRevision::RevisionType)
{
Q_UNUSED(localLocation)
return nullptr;
}
KDevelop::VcsJob * CvsPlugin::status(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
CvsJob* job = d->m_proxy->status(findWorkingDir(localLocations[0]),
localLocations,
(recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false);
return job;
}
KDevelop::VcsJob * CvsPlugin::unedit(const QUrl& localLocation)
{
CvsJob* job = d->m_proxy->unedit(findWorkingDir(localLocation),
QList() << localLocation);
return job;
}
KDevelop::VcsJob * CvsPlugin::edit(const QUrl& localLocation)
{
CvsJob* job = d->m_proxy->edit(findWorkingDir(localLocation),
QList() << localLocation);
return job;
}
KDevelop::VcsJob * CvsPlugin::copy(const QUrl & localLocationSrc, const QUrl & localLocationDstn)
{
bool ok = QFile::copy(localLocationSrc.toLocalFile(), localLocationDstn.path());
if (!ok) {
return nullptr;
}
QList listDstn;
listDstn << localLocationDstn;
CvsJob* job = d->m_proxy->add(findWorkingDir(localLocationDstn),
listDstn, true);
return job;
}
KDevelop::VcsJob * CvsPlugin::move(const QUrl &, const QUrl &)
{
return nullptr;
}
KDevelop::VcsJob * CvsPlugin::revert(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
KDevelop::VcsRevision rev;
CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]),
localLocations,
rev,
QStringLiteral("-C"),
(recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false,
false, false);
return job;
}
KDevelop::VcsJob * CvsPlugin::update(const QList & localLocations, const KDevelop::VcsRevision & rev, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
CvsJob* job = d->m_proxy->update(findWorkingDir(localLocations[0]),
localLocations,
rev,
QString(),
(recursion == KDevelop::IBasicVersionControl::Recursive) ? true : false,
false, false);
return job;
}
KDevelop::VcsJob * CvsPlugin::commit(const QString & message, const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
QString msg = message;
if (msg.isEmpty()) {
CommitDialog dlg;
if (dlg.exec() == QDialog::Accepted) {
msg = dlg.message();
}
}
CvsJob* job = d->m_proxy->commit(findWorkingDir(localLocations[0]),
localLocations,
msg);
return job;
}
KDevelop::VcsJob * CvsPlugin::diff(const QUrl & fileOrDirectory, const KDevelop::VcsRevision & srcRevision, const KDevelop::VcsRevision & dstRevision, KDevelop::VcsDiff::Type, KDevelop::IBasicVersionControl::RecursionMode)
{
CvsJob* job = d->m_proxy->diff(fileOrDirectory, srcRevision, dstRevision, QStringLiteral("-uN")/*always unified*/);
return job;
}
KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, unsigned long limit)
{
Q_UNUSED(limit)
CvsJob* job = d->m_proxy->log(localLocation, rev);
return job;
}
KDevelop::VcsJob * CvsPlugin::log(const QUrl & localLocation, const KDevelop::VcsRevision & rev, const KDevelop::VcsRevision & limit)
{
Q_UNUSED(limit)
return log(localLocation, rev, 0);
}
KDevelop::VcsJob * CvsPlugin::annotate(const QUrl & localLocation, const KDevelop::VcsRevision & rev)
{
CvsJob* job = d->m_proxy->annotate(localLocation, rev);
return job;
}
KDevelop::VcsJob * CvsPlugin::resolve(const QList & localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(localLocations);
Q_UNUSED(recursion);
return nullptr;
}
KDevelop::VcsJob * CvsPlugin::import(const QString& commitMessage, const QUrl& sourceDirectory, const KDevelop::VcsLocation& destinationRepository)
{
if (commitMessage.isEmpty()
|| !sourceDirectory.isLocalFile()
|| !destinationRepository.isValid()
|| destinationRepository.type() != KDevelop::VcsLocation::RepositoryLocation) {
return nullptr;
}
qCDebug(PLUGIN_CVS) << "CVS Import requested "
<< "src:" << sourceDirectory.toLocalFile()
<< "srv:" << destinationRepository.repositoryServer()
<< "module:" << destinationRepository.repositoryModule();
CvsJob* job = d->m_proxy->import(sourceDirectory,
destinationRepository.repositoryServer(),
destinationRepository.repositoryModule(),
destinationRepository.userData().toString(),
destinationRepository.repositoryTag(),
commitMessage);
return job;
}
KDevelop::VcsJob * CvsPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
if (!destinationDirectory.isLocalFile()
|| !sourceRepository.isValid()
|| sourceRepository.type() != KDevelop::VcsLocation::RepositoryLocation) {
return nullptr;
}
qCDebug(PLUGIN_CVS) << "CVS Checkout requested "
<< "dest:" << destinationDirectory.toLocalFile()
<< "srv:" << sourceRepository.repositoryServer()
<< "module:" << sourceRepository.repositoryModule()
<< "branch:" << sourceRepository.repositoryBranch() << endl;
CvsJob* job = d->m_proxy->checkout(destinationDirectory,
sourceRepository.repositoryServer(),
sourceRepository.repositoryModule(),
QString(),
sourceRepository.repositoryBranch(),
true, true);
return job;
}
QString CvsPlugin::name() const
{
return i18n("CVS");
}
KDevelop::VcsImportMetadataWidget* CvsPlugin::createImportMetadataWidget(QWidget* parent)
{
return new ImportMetadataWidget(parent);
}
KDevelop::VcsLocationWidget* CvsPlugin::vcsLocation(QWidget* parent) const
{
return new KDevelop::StandardVcsLocationWidget(parent);
}
// End: KDevelop::IBasicVersionControl
#include "cvsplugin.moc"
diff --git a/plugins/execute/executeplugin.cpp b/plugins/execute/executeplugin.cpp
index 2a041a0535..a1a24a8c74 100644
--- a/plugins/execute/executeplugin.cpp
+++ b/plugins/execute/executeplugin.cpp
@@ -1,252 +1,251 @@
/*
* This file is part of KDevelop
*
* Copyright 2007 Hamish Rodda
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "executeplugin.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "nativeappconfig.h"
#include "debug.h"
#include
#include
#include