Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Make SourceFormatter plugins enablable, hide actions with no plugins

Summary:
Source formatting is a feature seemingly only used by some.
So it makes sense to allow disabling this feature, for some minimal smaller
runtime footprint and less unused clutter in the UI (like menu actions or
settings).

This patch achieves this to a good degree:
* makes the plugins astyle & customscript normal global plugins which can be
  enabled/disabled by the user
* removes the SourceFormatterController actions from the menus when there are
  no formatters available
* shows no source formatting settings page in the project settings
  when there are no formatters available

Not yet done is to hide the source formatting settings page from the
application settings dialog in case no formatters are available. That might
need some more custom logic all over the shell code, which is not so nice.
Instead the option should be investigated to make the source formatting
controller a normal plugin with a suited interface, which then can be
queried by other code needing that service.

Test Plan:
See plugins astyle & customscript turn up in plugin selection settings.
Disable and enable both and see how actions in the main Edit menu and the
context menu on file items are present or not present, as well as the
formatting settings page in the project settings dialog being shown or not.

See also the formatting settings pages for projects and the global one only
offering the formatters of the enabled plugins.

Reviewers: #kdevelop, kfunk, mwolff

Reviewed By: #kdevelop, kfunk, mwolff

Subscribers: mwolff, kfunk, kdevelop-devel

Differential Revision: https://phabricator.kde.org/D8492
parent 6c2400fd
......@@ -58,6 +58,8 @@ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatterController : public QObject
* @param mime known mimetype of the url
*/
virtual ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) = 0;
///\return @c true if there are formatters at all, @c false otherwise
virtual bool hasFormatters() const = 0;
/** \return Whether this mime type is supported by any plugin.
*/
virtual bool isMimeTypeSupported(const QMimeType &mime) = 0;
......@@ -68,6 +70,9 @@ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatterController : public QObject
virtual void disableSourceFormatting(bool disable) = 0;
///\return Whether or not source formatting is enabled
virtual bool sourceFormattingEnabled() = 0;
Q_SIGNALS:
void hasFormattersChanged(bool hasFormatters);
};
}
......
......@@ -345,12 +345,16 @@ void MainWindow::initialize()
d, &MainWindowPrivate::activePartChanged);
connect( this, &MainWindow::activeViewChanged,
d, &MainWindowPrivate::changeActiveView);
connect(Core::self()->sourceFormatterControllerInternal(), &SourceFormatterController::hasFormattersChanged,
d, &MainWindowPrivate::updateSourceFormatterGuiClient);
foreach(IPlugin* plugin, Core::self()->pluginController()->loadedPlugins())
d->addPlugin(plugin);
guiFactory()->addClient(Core::self()->sessionController());
guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal());
if (Core::self()->sourceFormatterControllerInternal()->hasFormatters()) {
guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal());
}
// Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't
// seem to remember which actions where plugged in.
......
......@@ -47,6 +47,7 @@ Boston, MA 02110-1301, USA.
#include "mainwindow.h"
#include "textdocument.h"
#include "sessioncontroller.h"
#include "sourceformattercontroller.h"
#include "debug.h"
#include "ktexteditorpluginintegration.h"
#include "colorschemechooser.h"
......@@ -125,6 +126,17 @@ void MainWindowPrivate::removePlugin( IPlugin *plugin )
m_mainWindow->guiFactory()->removeClient( plugin );
}
void MainWindowPrivate::updateSourceFormatterGuiClient(bool hasFormatters)
{
auto sourceFormatterController = Core::self()->sourceFormatterControllerInternal();
auto guiFactory = m_mainWindow->guiFactory();
if (hasFormatters) {
guiFactory->addClient(sourceFormatterController);
} else {
guiFactory->removeClient(sourceFormatterController);
}
}
void MainWindowPrivate::activePartChanged(KParts::Part *part)
{
if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow)
......
......@@ -77,6 +77,7 @@ public:
public Q_SLOTS:
void addPlugin( KDevelop::IPlugin *plugin );
void removePlugin( KDevelop::IPlugin *plugin );
void updateSourceFormatterGuiClient(bool hasFormatters);
void activePartChanged(KParts::Part *part);
void mergeView(Sublime::View *view);
......
......@@ -74,6 +74,8 @@ namespace KDevelop
class SourceFormatterControllerPrivate
{
public:
// cache of formatter plugins, to avoid querying plugincontroller
QVector<ISourceFormatter*> sourceFormatters;
// GUI actions
QAction* formatTextAction;
QAction* formatFilesAction;
......@@ -127,20 +129,29 @@ SourceFormatterController::SourceFormatterController(QObject *parent)
d->formatTextAction->setText(i18n("&Reformat Source"));
d->formatTextAction->setToolTip(i18n("Reformat source using AStyle"));
d->formatTextAction->setWhatsThis(i18n("Source reformatting functionality using <b>astyle</b> library."));
d->formatTextAction->setEnabled(false);
connect(d->formatTextAction, &QAction::triggered, this, &SourceFormatterController::beautifySource);
d->formatLine = actionCollection()->addAction(QStringLiteral("edit_reformat_line"));
d->formatLine->setText(i18n("Reformat Line"));
d->formatLine->setToolTip(i18n("Reformat current line using AStyle"));
d->formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using <b>astyle</b> library."));
d->formatLine->setEnabled(false);
connect(d->formatLine, &QAction::triggered, this, &SourceFormatterController::beautifyLine);
d->formatFilesAction = actionCollection()->addAction(QStringLiteral("tools_astyle"));
d->formatFilesAction->setText(i18n("Reformat Files..."));
d->formatFilesAction->setToolTip(i18n("Format file(s) using the current theme"));
d->formatFilesAction->setWhatsThis(i18n("Formatting functionality using <b>astyle</b> library."));
d->formatFilesAction->setEnabled(false);
connect(d->formatFilesAction, &QAction::triggered, this, static_cast<void(SourceFormatterController::*)()>(&SourceFormatterController::formatFiles));
connect(Core::self()->pluginController(), &IPluginController::pluginLoaded,
this, &SourceFormatterController::pluginLoaded);
connect(Core::self()->pluginController(), &IPluginController::unloadingPlugin,
this, &SourceFormatterController::unloadingPlugin);
// connect to both documentActivated & documentClosed,
// otherwise we miss when the last document was closed
connect(Core::self()->documentController(), &IDocumentController::documentActivated,
......@@ -168,6 +179,46 @@ void SourceFormatterController::documentLoaded( IDocument* doc )
adaptEditorIndentationMode(doc->textDocument(), formatterForUrl(url, mime), url);
}
void SourceFormatterController::pluginLoaded(IPlugin* plugin)
{
ISourceFormatter* sourceFormatter = plugin->extension<ISourceFormatter>();
if (!sourceFormatter) {
return;
}
d->sourceFormatters << sourceFormatter;
resetUi();
emit formatterLoaded(sourceFormatter);
// with one plugin now added, hasFormatters turned to true, so report to listeners
if (d->sourceFormatters.size() == 1) {
emit hasFormattersChanged(true);
}
}
void SourceFormatterController::unloadingPlugin(IPlugin* plugin)
{
ISourceFormatter* sourceFormatter = plugin->extension<ISourceFormatter>();
if (!sourceFormatter) {
return;
}
const int idx = d->sourceFormatters.indexOf(sourceFormatter);
Q_ASSERT(idx != -1);
d->sourceFormatters.remove(idx);
resetUi();
emit formatterUnloading(sourceFormatter);
if (d->sourceFormatters.isEmpty()) {
emit hasFormattersChanged(false);
}
}
void SourceFormatterController::initialize()
{
}
......@@ -212,9 +263,7 @@ ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const
if (knownFormatters.contains(mime.name()))
return knownFormatters[mime.name()];
QList<IPlugin*> plugins = Core::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") );
foreach( IPlugin* p, plugins) {
ISourceFormatter *iformatter = p->extension<ISourceFormatter>();
foreach (ISourceFormatter* iformatter, d->sourceFormatters) {
QSharedPointer<SourceFormatter> formatter(createFormatterForPlugin(iformatter));
if( formatter->supportedMimeTypes().contains(mime.name()) ) {
knownFormatters[mime.name()] = iformatter;
......@@ -275,7 +324,13 @@ ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl& url, co
return nullptr;
}
return Core::self()->pluginControllerInternal()->extensionForPlugin<ISourceFormatter>( QStringLiteral("org.kdevelop.ISourceFormatter"), formatterinfo.at(0) );
foreach (ISourceFormatter* iformatter, d->sourceFormatters) {
if (iformatter->name() == formatterinfo.first()) {
return iformatter;
}
}
return nullptr;
}
bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime)
......@@ -377,11 +432,13 @@ void SourceFormatterController::updateFormatTextAction()
{
bool enabled = false;
IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument();
if (doc) {
QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url());
if (isMimeTypeSupported(mime))
enabled = true;
if (!d->sourceFormatters.isEmpty()) {
IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument();
if (doc) {
QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url());
if (isMimeTypeSupported(mime))
enabled = true;
}
}
d->formatLine->setEnabled(enabled);
......@@ -616,6 +673,10 @@ KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(K
d->urls.clear();
d->prjItems.clear();
if (d->sourceFormatters.isEmpty()) {
return ext;
}
if (context->hasType(KDevelop::Context::EditorContext))
{
if (d->formatTextAction->isEnabled())
......@@ -661,4 +722,21 @@ bool SourceFormatterController::sourceFormattingEnabled()
return d->enabled;
}
bool SourceFormatterController::hasFormatters() const
{
return !d->sourceFormatters.isEmpty();
}
QVector<ISourceFormatter*> SourceFormatterController::formatters() const
{
return d->sourceFormatters;
}
void SourceFormatterController::resetUi()
{
d->formatFilesAction->setEnabled(!d->sourceFormatters.isEmpty());
updateFormatTextAction();
}
}
......@@ -25,6 +25,7 @@ Boston, MA 02110-1301, USA.
#include <interfaces/isourceformatter.h>
#include <QSet>
#include <QVector>
#include <QMimeType>
#include <kxmlguiclient.h>
......@@ -102,6 +103,7 @@ public:
* The source formatter is then ready to use on a file.
*/
ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) override;
bool hasFormatters() const override;
/** \return Whether this mime type is supported by any plugin.
*/
bool isMimeTypeSupported(const QMimeType& mime) override;
......@@ -132,12 +134,21 @@ public:
void disableSourceFormatting(bool disable) override;
bool sourceFormattingEnabled() override;
QVector<KDevelop::ISourceFormatter*> formatters() const;
Q_SIGNALS:
void formatterLoaded(KDevelop::ISourceFormatter* ifmt);
void formatterUnloading(KDevelop::ISourceFormatter* ifmt);
private Q_SLOTS:
void updateFormatTextAction();
void beautifySource();
void beautifyLine();
void formatFiles();
void documentLoaded( KDevelop::IDocument* );
void pluginLoaded(KDevelop::IPlugin* plugin);
void unloadingPlugin(KDevelop::IPlugin* plugin);
private:
/** \return A modeline string (to add at the end or the beginning of a file)
* corresponding to the settings of the active language.
......@@ -152,6 +163,8 @@ private:
void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter,
const QUrl& url, bool ignoreModeline = false);
void resetUi();
private:
const QScopedPointer<class SourceFormatterControllerPrivate> d;
};
......
......@@ -112,6 +112,15 @@ SourceFormatterSelectionEdit::SourceFormatterSelectionEdit(QWidget* parent)
iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false);
iface->setConfigValue(QStringLiteral("icon-bar"), false);
}
SourceFormatterController* controller = Core::self()->sourceFormatterControllerInternal();
connect(controller, &SourceFormatterController::formatterLoaded,
this, &SourceFormatterSelectionEdit::addSourceFormatter);
connect(controller, &SourceFormatterController::formatterUnloading,
this, &SourceFormatterSelectionEdit::removeSourceFormatter);
for (auto* formatter : controller->formatters()) {
addSourceFormatter(formatter);
}
}
SourceFormatterSelectionEdit::~SourceFormatterSelectionEdit()
......@@ -125,58 +134,76 @@ static void selectAvailableStyle(LanguageSettings& lang)
lang.selectedStyle = *lang.selectedFormatter->styles.begin();
}
void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config)
void SourceFormatterSelectionEdit::addSourceFormatter(ISourceFormatter* ifmt)
{
SourceFormatterController* fmtctrl = Core::self()->sourceFormatterControllerInternal();
QList<KDevelop::IPlugin*> plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") );
foreach( KDevelop::IPlugin* plugin, plugins )
{
KDevelop::ISourceFormatter* ifmt = plugin->extension<ISourceFormatter>();
auto info = KDevelop::Core::self()->pluginControllerInternal()->pluginInfo( plugin );
KDevelop::SourceFormatter* formatter;
FormatterMap::const_iterator iter = d->formatters.constFind(ifmt->name());
if (iter == d->formatters.constEnd()) {
formatter = fmtctrl->createFormatterForPlugin(ifmt);
d->formatters[ifmt->name()] = formatter;
} else {
formatter = iter.value();
}
foreach ( const SourceFormatterStyle* style, formatter->styles ) {
foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) {
QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType);
if (!mime.isValid()) {
qCWarning(SHELL) << "plugin" << info.name() << "supports unknown mimetype entry" << item.mimeType;
continue;
}
QString languageName = item.highlightMode;
LanguageSettings& l = d->languages[languageName];
l.mimetypes.append(mime);
l.formatters.insert( formatter );
SourceFormatter* formatter;
FormatterMap::const_iterator iter = d->formatters.constFind(ifmt->name());
if (iter == d->formatters.constEnd()) {
formatter = Core::self()->sourceFormatterControllerInternal()->createFormatterForPlugin(ifmt);
d->formatters[ifmt->name()] = formatter;
} else {
qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "loading which was already seen before by SourceFormatterSelectionEdit";
return;
}
foreach ( const SourceFormatterStyle* style, formatter->styles ) {
foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) {
QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType);
if (!mime.isValid()) {
qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "supports unknown mimetype entry" << item.mimeType;
continue;
}
QString languageName = item.highlightMode;
LanguageSettings& l = d->languages[languageName];
l.mimetypes.append(mime);
l.formatters.insert( formatter );
// init selection if needed
if (!l.selectedFormatter) {
l.selectedFormatter = formatter;
selectAvailableStyle(l);
}
}
}
// Sort the languages, preferring firstly active, then loaded languages
QList<QString> sortedLanguages;
resetUi();
}
foreach(const auto language,
KDevelop::ICore::self()->languageController()->activeLanguages() +
KDevelop::ICore::self()->languageController()->loadedLanguages())
{
if (d->languages.contains(language->name()) && !sortedLanguages.contains(language->name())) {
sortedLanguages.push_back( language->name() );
}
void SourceFormatterSelectionEdit::removeSourceFormatter(ISourceFormatter* ifmt)
{
auto iter = d->formatters.find(ifmt->name());
if (iter == d->formatters.end()) {
qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "unloading which was not seen before by SourceFormatterSelectionEdit";
return;
}
d->formatters.erase(iter);
auto formatter = iter.value();
foreach (const QString& name, d->languages.keys()) {
if( !sortedLanguages.contains( name ) )
sortedLanguages.push_back( name );
auto languageIter = d->languages.begin();
while (languageIter != d->languages.end()) {
LanguageSettings& l = languageIter.value();
l.formatters.remove(formatter);
if (l.formatters.isEmpty()) {
languageIter = d->languages.erase(languageIter);
} else {
// reset selected formatter if needed
if (l.selectedFormatter == formatter) {
l.selectedFormatter = *l.formatters.begin();
selectAvailableStyle(l);
}
++languageIter;
}
}
delete formatter;
foreach( const QString& name, sortedLanguages )
{
resetUi();
}
void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config)
{
for (auto languageIter = d->languages.begin(); languageIter != d->languages.end(); ++languageIter) {
// Pick the first appropriate mimetype for this language
LanguageSettings& l = d->languages[name];
LanguageSettings& l = languageIter.value();
const QList<QMimeType> mimetypes = l.mimetypes;
foreach (const QMimeType& mimetype, mimetypes) {
QStringList formatterAndStyleName = config.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts);
......@@ -205,6 +232,29 @@ void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config)
selectAvailableStyle(l);
}
}
resetUi();
}
void SourceFormatterSelectionEdit::resetUi()
{
// Sort the languages, preferring firstly active, then loaded languages
QList<QString> sortedLanguages;
foreach(const auto language,
KDevelop::ICore::self()->languageController()->activeLanguages() +
KDevelop::ICore::self()->languageController()->loadedLanguages())
{
if (d->languages.contains(language->name()) && !sortedLanguages.contains(language->name())) {
sortedLanguages.push_back( language->name() );
}
}
foreach (const QString& name, d->languages.keys()) {
if( !sortedLanguages.contains( name ) )
sortedLanguages.push_back( name );
}
bool b = blockSignals( true );
d->ui.cbLanguages->blockSignals(!b);
d->ui.cbFormatters->blockSignals(!b);
......@@ -222,6 +272,7 @@ void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config)
} else
{
d->ui.cbLanguages->setCurrentIndex(0);
d->ui.cbLanguages->setEnabled(true);
selectLanguage( 0 );
}
updatePreview();
......
......@@ -32,6 +32,7 @@ class QListWidgetItem;
namespace KDevelop
{
class SourceFormatterStyle;
class ISourceFormatter;
class KDEVPLATFORMSHELL_EXPORT SourceFormatterSelectionEdit : public QWidget
{
......@@ -49,6 +50,9 @@ Q_SIGNALS:
void changed();
private Q_SLOTS:
void addSourceFormatter(KDevelop::ISourceFormatter* ifmt);
void removeSourceFormatter(KDevelop::ISourceFormatter* ifmt);
void deleteStyle();
void editStyle();
void newStyle();
......@@ -58,6 +62,7 @@ private Q_SLOTS:
void styleNameChanged(QListWidgetItem* );
private:
void resetUi();
void updatePreview();
QListWidgetItem* addStyle(const KDevelop::SourceFormatterStyle& s);
void enableStyleButtons();
......
......@@ -59,6 +59,6 @@
"X-KDevelop-Interfaces": [
"org.kdevelop.ISourceFormatter"
],
"X-KDevelop-LoadMode": "AlwaysOn",
"X-KDevelop-Category": "Global",
"X-KDevelop-Mode": "NoGUI"
}
......@@ -59,6 +59,6 @@
"X-KDevelop-Interfaces": [
"org.kdevelop.ISourceFormatter"
],
"X-KDevelop-LoadMode": "AlwaysOn",
"X-KDevelop-Category": "Global",
"X-KDevelop-Mode": "NoGUI"
}
......@@ -21,6 +21,8 @@
#include "config/projectconfigpage.h"
#include <interfaces/icore.h>
#include <interfaces/isourceformattercontroller.h>
#include <project/projectconfigpage.h>
#include <KPluginFactory>
......@@ -38,7 +40,8 @@ SourceFormatterPlugin::~SourceFormatterPlugin() = default;
int SourceFormatterPlugin::perProjectConfigPages() const
{
return 1;
const auto hasFormatters = KDevelop::ICore::self()->sourceFormatterController()->hasFormatters();
return hasFormatters ? 1 : 0;
}
KDevelop::ConfigPage* SourceFormatterPlugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment