Commit e195c872 authored by Lorenz Haas's avatar Lorenz Haas Committed by Lorenz Haas

Beautifier: Add option to automatically format files on save

Change-Id: I72fb3f4b728df7ef3e449c1202df9cbb0279dde4
Reviewed-by: default avatarDavid Schulz <david.schulz@theqtcompany.com>
parent 41cf9d7b
......@@ -129,4 +129,21 @@
file in this case when using Clang, select the
\uicontrol {Format entire file if no text was selected} check box in the
\uicontrol {Clang Format} options.
To automatically format files when they are saved, select \uicontrol Tools >
\uicontrol Beautifier > \uicontrol General:
\list 1
\li In the \uicontrol Tool field, select the tool for formatting.
\li In the \uicontrol {Restrict to MIME types} field, specify a
semicolon-separated list of MIME types. One of these types must
match the MIME type of the file that is auto formatted.
An empty list accepts all files.
\li Select the \uicontrol {Restrict to files contained in the current
project} check box to only auto format files in the current project.
\endlist
*/
......@@ -33,7 +33,6 @@
#include "../beautifierconstants.h"
#include "../beautifierplugin.h"
#include "../command.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
......@@ -81,6 +80,11 @@ bool ArtisticStyle::initialize()
return true;
}
QString ArtisticStyle::id() const
{
return QLatin1String(Constants::ArtisticStyle::DISPLAY_NAME);
}
void ArtisticStyle::updateActions(Core::IEditor *editor)
{
m_formatFile->setEnabled(editor && editor->document()->id() == CppEditor::Constants::CPPEDITOR_ID);
......@@ -134,6 +138,12 @@ QString ArtisticStyle::configurationFile() const
return QString();
}
Command ArtisticStyle::command() const
{
const QString cfgFile = configurationFile();
return cfgFile.isEmpty() ? Command() : command(cfgFile);
}
Command ArtisticStyle::command(const QString &cfgFile) const
{
Command command;
......
......@@ -26,7 +26,6 @@
#pragma once
#include "../beautifierabstracttool.h"
#include "../command.h"
QT_FORWARD_DECLARE_CLASS(QAction)
......@@ -47,8 +46,10 @@ public:
explicit ArtisticStyle(BeautifierPlugin *parent = nullptr);
virtual ~ArtisticStyle();
bool initialize() override;
QString id() const override;
void updateActions(Core::IEditor *editor) override;
QList<QObject *> autoReleaseObjects() override;
Command command() const override;
private:
void formatFile();
......
......@@ -9,6 +9,8 @@ HEADERS += \
configurationdialog.h \
configurationeditor.h \
configurationpanel.h \
generaloptionspage.h \
generalsettings.h \
artisticstyle/artisticstyleconstants.h \
artisticstyle/artisticstyle.h \
artisticstyle/artisticstyleoptionspage.h \
......@@ -29,6 +31,8 @@ SOURCES += \
configurationdialog.cpp \
configurationeditor.cpp \
configurationpanel.cpp \
generaloptionspage.cpp \
generalsettings.cpp \
artisticstyle/artisticstyle.cpp \
artisticstyle/artisticstyleoptionspage.cpp \
artisticstyle/artisticstylesettings.cpp \
......@@ -42,6 +46,7 @@ SOURCES += \
FORMS += \
configurationdialog.ui \
configurationpanel.ui \
generaloptionspage.ui \
artisticstyle/artisticstyleoptionspage.ui \
clangformat/clangformatoptionspage.ui \
uncrustify/uncrustifyoptionspage.ui \
......
......@@ -30,6 +30,11 @@ QtcPlugin {
"configurationpanel.cpp",
"configurationpanel.h",
"configurationpanel.ui"
"generaloptionspage.cpp",
"generaloptionspage.h",
"generaloptionspage.ui"
"generalsettings.cpp",
"generalsettings.h"
]
Group {
......
......@@ -25,6 +25,8 @@
#pragma once
#include "command.h"
#include <QList>
#include <QObject>
......@@ -41,9 +43,17 @@ public:
explicit BeautifierAbstractTool(QObject *parent = nullptr) : QObject(parent) {}
virtual ~BeautifierAbstractTool() {}
virtual QString id() const = 0;
virtual bool initialize() = 0;
virtual void updateActions(Core::IEditor *editor) = 0;
virtual QList<QObject *> autoReleaseObjects() = 0;
/**
* Returns the tool's command to format an entire file.
*
* @note The received command may be invalid.
*/
virtual Command command() const = 0;
};
} // namespace Internal
......
......@@ -35,6 +35,7 @@ const char MENU_ID[] = "Beautifier.Menu";
const char OPTION_CATEGORY[] = "II.Beautifier";
const char OPTION_TR_CATEGORY[] = QT_TRANSLATE_NOOP("Beautifier", "Beautifier");
const char OPTION_CATEGORY_ICON[] = ":/beautifier/images/beautifier.png";
const char OPTION_GENERAL_ID[] = "aaa.General";
const char SETTINGS_GROUP[] = "Beautifier";
const char SETTINGS_DIRNAME[] = "beautifier";
const char DOCUMENTATION_DIRNAME[] = "documentation";
......
......@@ -26,6 +26,8 @@
#include "beautifierplugin.h"
#include "beautifierconstants.h"
#include "generaloptionspage.h"
#include "generalsettings.h"
#include "artisticstyle/artisticstyle.h"
#include "clangformat/clangformat.h"
......@@ -35,14 +37,22 @@
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/messagemanager.h>
#include <cppeditor/cppeditorconstants.h>
#include <diffeditor/differ.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projecttree.h>
#include <texteditor/convenience.h>
#include <texteditor/textdocument.h>
#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h>
#include <texteditor/texteditorconstants.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/synchronousprocess.h>
......@@ -161,37 +171,61 @@ QString sourceData(TextEditorWidget *editor, int startPos, int endPos)
: Convenience::textAt(editor->textCursor(), startPos, (endPos - startPos));
}
bool isAutoFormatApplicable(const QString &filePath, const QList<Utils::MimeType> &allowedMimeTypes)
{
if (allowedMimeTypes.isEmpty())
return true;
const Utils::MimeDatabase mdb;
const QList<Utils::MimeType> fileMimeTypes = mdb.mimeTypesForFileName(filePath);
auto inheritedByFileMimeTypes = [&fileMimeTypes](const Utils::MimeType &mimeType){
const QString name = mimeType.name();
return Utils::anyOf(fileMimeTypes, [&name](const Utils::MimeType &fileMimeType){
return fileMimeType.inherits(name);
});
};
return Utils::anyOf(allowedMimeTypes, inheritedByFileMimeTypes);
}
bool BeautifierPlugin::initialize(const QStringList &arguments, QString *errorString)
{
Q_UNUSED(arguments)
Q_UNUSED(errorString)
m_tools << new ArtisticStyle::ArtisticStyle(this);
m_tools << new ClangFormat::ClangFormat(this);
m_tools << new Uncrustify::Uncrustify(this);
Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::MENU_ID);
menu->menu()->setTitle(QCoreApplication::translate("Beautifier", Constants::OPTION_TR_CATEGORY));
menu->setOnAllDisabledBehavior(Core::ActionContainer::Show);
Core::ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu);
m_tools << new ArtisticStyle::ArtisticStyle(this);
m_tools << new ClangFormat::ClangFormat(this);
m_tools << new Uncrustify::Uncrustify(this);
QStringList toolIds;
toolIds.reserve(m_tools.count());
for (BeautifierAbstractTool *tool : m_tools) {
toolIds << tool->id();
tool->initialize();
const QList<QObject *> autoReleasedObjects = tool->autoReleaseObjects();
for (QObject *object : autoReleasedObjects)
addAutoReleasedObject(object);
}
m_generalSettings = new GeneralSettings;
auto settingsPage = new GeneralOptionsPage(m_generalSettings, toolIds, this);
addAutoReleasedObject(settingsPage);
updateActions();
return true;
}
void BeautifierPlugin::extensionsInitialized()
{
if (const Core::EditorManager *editorManager = Core::EditorManager::instance()) {
connect(editorManager, &Core::EditorManager::currentEditorChanged,
this, &BeautifierPlugin::updateActions);
}
const Core::EditorManager *editorManager = Core::EditorManager::instance();
connect(editorManager, &Core::EditorManager::currentEditorChanged,
this, &BeautifierPlugin::updateActions);
connect(editorManager, &Core::EditorManager::aboutToSave,
this, &BeautifierPlugin::autoFormatOnSave);
}
ExtensionSystem::IPlugin::ShutdownFlag BeautifierPlugin::aboutToShutdown()
......@@ -205,6 +239,42 @@ void BeautifierPlugin::updateActions(Core::IEditor *editor)
tool->updateActions(editor);
}
void BeautifierPlugin::autoFormatOnSave(Core::IDocument *document)
{
if (!m_generalSettings->autoFormatOnSave())
return;
// Check that we are dealing with a cpp editor
if (document->id() != CppEditor::Constants::CPPEDITOR_ID)
return;
const QString filePath = document->filePath().toString();
if (!isAutoFormatApplicable(filePath, m_generalSettings->autoFormatMime()))
return;
// Check if file is contained in the current project (if wished)
if (m_generalSettings->autoFormatOnlyCurrentProject()) {
const ProjectExplorer::Project *pro = ProjectExplorer::ProjectTree::currentProject();
if (!pro || !pro->files(ProjectExplorer::Project::SourceFiles).contains(filePath))
return;
}
// Find tool to use by id and format file!
const QString id = m_generalSettings->autoFormatTool();
auto tool = std::find_if(m_tools.constBegin(), m_tools.constEnd(),
[&id](const BeautifierAbstractTool *t){return t->id() == id;});
if (tool != m_tools.constEnd()) {
const Command command = (*tool)->command();
if (!command.isValid())
return;
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForDocument(document);
if (editors.isEmpty())
return;
if (TextEditorWidget* widget = qobject_cast<TextEditorWidget *>(editors.first()->widget()))
formatEditor(widget, command);
}
}
void BeautifierPlugin::formatCurrentFile(const Command &command, int startPos, int endPos)
{
if (TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget())
......
......@@ -32,13 +32,17 @@
#include <QPlainTextEdit>
#include <QPointer>
namespace Core { class IEditor; }
namespace TextEditor { class TextEditorWidget; }
namespace Core {
class IDocument;
class IEditor;
}
namespace TextEditor {class TextEditorWidget;}
namespace Beautifier {
namespace Internal {
class BeautifierAbstractTool;
class GeneralSettings;
struct FormatTask
{
......@@ -82,12 +86,16 @@ public:
private:
void updateActions(Core::IEditor *editor = nullptr);
QList<BeautifierAbstractTool *> m_tools;
GeneralSettings *m_generalSettings = nullptr;
QHash<QObject*, QMetaObject::Connection> m_autoFormatConnections;
void formatEditor(TextEditor::TextEditorWidget *editor, const Command &command,
int startPos = -1, int endPos = 0);
void formatEditorAsync(TextEditor::TextEditorWidget *editor, const Command &command,
int startPos = -1, int endPos = 0);
void checkAndApplyTask(const FormatTask &task);
void updateEditorText(QPlainTextEdit *editor, const QString &text);
void autoFormatOnSave(Core::IDocument *document);
};
} // namespace Internal
......
......@@ -63,6 +63,11 @@ ClangFormat::~ClangFormat()
delete m_settings;
}
QString ClangFormat::id() const
{
return QLatin1String(Constants::ClangFormat::DISPLAY_NAME);
}
bool ClangFormat::initialize()
{
Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::ClangFormat::MENU_ID);
......@@ -120,7 +125,7 @@ void ClangFormat::formatSelectedText()
}
}
Command ClangFormat::command(int offset, int length) const
Command ClangFormat::command() const
{
Command command;
command.setExecutable(m_settings->command());
......@@ -136,14 +141,17 @@ Command ClangFormat::command(int offset, int length) const
command.addOption("-assume-filename=" + path + QDir::separator() + "%filename");
}
if (offset != -1) {
command.addOption("-offset=" + QString::number(offset));
command.addOption("-length=" + QString::number(length));
}
return command;
}
Command ClangFormat::command(int offset, int length) const
{
Command c = command();
c.addOption("-offset=" + QString::number(offset));
c.addOption("-length=" + QString::number(length));
return c;
}
} // namespace ClangFormat
} // namespace Internal
} // namespace Beautifier
......@@ -26,7 +26,6 @@
#pragma once
#include "../beautifierabstracttool.h"
#include "../command.h"
QT_FORWARD_DECLARE_CLASS(QAction)
......@@ -46,9 +45,11 @@ class ClangFormat : public BeautifierAbstractTool
public:
explicit ClangFormat(BeautifierPlugin *parent = nullptr);
virtual ~ClangFormat();
QString id() const override;
bool initialize() override;
void updateActions(Core::IEditor *editor) override;
QList<QObject *> autoReleaseObjects() override;
Command command() const override;
private:
void formatFile();
......@@ -57,7 +58,7 @@ private:
QAction *m_formatFile = nullptr;
QAction *m_formatRange = nullptr;
ClangFormatSettings *m_settings;
Command command(int offset = -1, int length = -1) const;
Command command(int offset, int length) const;
};
} // namespace ClangFormat
......
......@@ -28,6 +28,11 @@
namespace Beautifier {
namespace Internal {
bool Command::isValid() const
{
return !m_executable.isEmpty();
}
QString Command::executable() const
{
return m_executable;
......
......@@ -39,6 +39,8 @@ public:
PipeProcessing
};
bool isValid() const;
QString executable() const;
void setExecutable(const QString &executable);
......
/****************************************************************************
**
** Copyright (C) 2016 Lorenz Haas
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "generaloptionspage.h"
#include "ui_generaloptionspage.h"
#include "beautifierconstants.h"
#include "generalsettings.h"
#include <coreplugin/icore.h>
#include <QTextStream>
namespace Beautifier {
namespace Internal {
GeneralOptionsPageWidget::GeneralOptionsPageWidget(GeneralSettings *settings,
const QStringList &toolIds, QWidget *parent) :
QWidget(parent),
ui(new Ui::GeneralOptionsPage),
m_settings(settings)
{
ui->setupUi(this);
ui->autoFormatTool->addItems(toolIds);
restore();
}
GeneralOptionsPageWidget::~GeneralOptionsPageWidget()
{
delete ui;
}
void GeneralOptionsPageWidget::restore()
{
ui->autoFormat->setChecked(m_settings->autoFormatOnSave());
const int index = ui->autoFormatTool->findText(m_settings->autoFormatTool());
ui->autoFormatTool->setCurrentIndex(qMax(index, 0));
ui->autoFormatMime->setText(m_settings->autoFormatMimeAsString());
ui->autoFormatOnlyCurrentProject->setChecked(m_settings->autoFormatOnlyCurrentProject());
}
void GeneralOptionsPageWidget::apply(bool *autoFormatChanged)
{
if (autoFormatChanged)
*autoFormatChanged = (m_settings->autoFormatOnSave() != ui->autoFormat->isChecked());
m_settings->setAutoFormatOnSave(ui->autoFormat->isChecked());
m_settings->setAutoFormatTool(ui->autoFormatTool->currentText());
m_settings->setAutoFormatMime(ui->autoFormatMime->text());
m_settings->setAutoFormatOnlyCurrentProject(ui->autoFormatOnlyCurrentProject->isChecked());
m_settings->save();
}
GeneralOptionsPage::GeneralOptionsPage(GeneralSettings *settings, const QStringList &toolIds,
QObject *parent) :
IOptionsPage(parent),
m_settings(settings),
m_toolIds(toolIds)
{
setId(Constants::OPTION_GENERAL_ID);
setDisplayName(tr("General"));
setCategory(Constants::OPTION_CATEGORY);
setDisplayCategory(QCoreApplication::translate("Beautifier", Constants::OPTION_TR_CATEGORY));
setCategoryIcon(Constants::OPTION_CATEGORY_ICON);
}
QWidget *GeneralOptionsPage::widget()
{
m_settings->read();
if (!m_widget)
m_widget = new GeneralOptionsPageWidget(m_settings, m_toolIds);
m_widget->restore();
return m_widget;
}
void GeneralOptionsPage::apply()
{
if (m_widget) {
bool autoFormat = false;
m_widget->apply(&autoFormat);
if (autoFormat)
emit autoFormatChanged();
}
}
void GeneralOptionsPage::finish()
{
}
} // namespace Internal
} // namespace Beautifier
/****************************************************************************
**
** Copyright (C) 2016 Lorenz Haas
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <coreplugin/dialogs/ioptionspage.h>
#include <QPointer>
#include <QWidget>
namespace Beautifier {
namespace Internal {
class GeneralSettings;
namespace Ui { class GeneralOptionsPage; }
class GeneralOptionsPageWidget : public QWidget
{
Q_OBJECT
public:
explicit GeneralOptionsPageWidget(GeneralSettings *settings, const QStringList &toolIds,
QWidget *parent = nullptr);
virtual ~GeneralOptionsPageWidget();
void restore();
void apply(bool *autoFormatChanged);
private:
Ui::GeneralOptionsPage *ui;
GeneralSettings *m_settings;
};
class GeneralOptionsPage : public Core::IOptionsPage
{
Q_OBJECT
public:
explicit GeneralOptionsPage(GeneralSettings *settings, const QStringList &toolIds,
QObject *parent = nullptr);
QWidget *widget() override;
void apply() override;
void finish() override;
signals:
void autoFormatChanged();
private:
QPointer<GeneralOptionsPageWidget> m_widget;
GeneralSettings *m_settings;
QStringList m_toolIds;
};
} // namespace Internal
} // namespace Beautifier
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Beautifier::Internal::GeneralOptionsPage</class>
<widget class="QWidget" name="Beautifier::Internal::GeneralOptionsPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>817</width>
<height>631</height>
</rect>