Commit 4988b41b authored by Lorenz Haas's avatar Lorenz Haas Committed by David Schulz

Introduce new Beautifier plug-in

Beautifier is a plug-in for source code beautifiers.
Currently Artistic Style, Clang-format and Uncrustify are supported.

Task-number: QTCREATORBUG-7489
Change-Id: I8dde9a8ba6a6a55537bfd450ec82ef966ed283c0
Reviewed-by: default avatarDavid Schulz <david.schulz@digia.com>
parent f2ae69b1
<plugin name=\"Beautifier\" version=\"$$QTCREATOR_VERSION\" compatVersion=\"$$QTCREATOR_VERSION\" experimental=\"true\">
<vendor>Lorenz Haas</vendor>
<copyright>(C) 2014 Lorenz Haas</copyright>
<license>
Commercial Usage
Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Digia.
GNU Lesser General Public License Usage
Alternatively, this plugin may be used under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. Please review the following information to ensure the GNU Lesser General Public License version 2.1 requirements will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
</license>
<description>Format source files with the help of beautifiers like AStyle, uncrustify or clang-format.</description>
<category>C++</category>
<url>http://www.qt-project.org</url>
$$dependencyList
</plugin>
/**************************************************************************
**
** Copyright (c) 2014 Lorenz Haas
** Contact: http://www.qt-project.org/legal
**
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "abstractsettings.h"
#include "beautifierconstants.h"
#include "beautifierplugin.h"
#include <coreplugin/icore.h>
#include <utils/fileutils.h>
#include <QDomDocument>
#include <QDir>
#include <QDomElement>
#include <QFile>
#include <QFileInfo>
namespace Beautifier {
namespace Internal {
AbstractSettings::AbstractSettings(const QString &name, const QString &ending)
: m_name(name)
, m_ending(ending)
, m_styleDir(Core::ICore::userResourcePath() + QLatin1Char('/')
+ QLatin1String(Beautifier::Constants::SETTINGS_DIRNAME) + QLatin1Char('/')
+ m_name)
{
}
AbstractSettings::~AbstractSettings()
{
}
QStringList AbstractSettings::completerWords()
{
return QStringList();
}
QStringList AbstractSettings::styles() const
{
QStringList list = m_styles.keys();
list.sort(Qt::CaseInsensitive);
return list;
}
QString AbstractSettings::style(const QString &key) const
{
return m_styles.value(key);
}
bool AbstractSettings::styleExists(const QString &key) const
{
return m_styles.contains(key);
}
bool AbstractSettings::styleIsReadOnly(const QString &key)
{
QFileInfo fi(m_styleDir.absoluteFilePath(key + m_ending));
if (!fi.exists()) {
// newly added style which was not saved yet., thus it is not read only.
//TODO In a later version when we have predefined styles in Core::ICore::resourcePath()
// we need to check if it is a read only global config file...
return false;
}
return !fi.isWritable();
}
void AbstractSettings::setStyle(const QString &key, const QString &value)
{
m_styles.insert(key, value);
m_changedStyles.insert(key);
}
void AbstractSettings::removeStyle(const QString &key)
{
m_styles.remove(key);
m_stylesToRemove << key + m_ending;
}
void AbstractSettings::replaceStyle(const QString &oldKey, const QString &newKey,
const QString &value)
{
// Set value regardles if keys are equal
m_styles.insert(newKey, value);
if (oldKey != newKey)
removeStyle(oldKey);
m_changedStyles.insert(newKey);
}
QString AbstractSettings::styleFileName(const QString &key) const
{
return m_styleDir.absoluteFilePath(key + m_ending);
}
QString AbstractSettings::command() const
{
return m_command;
}
void AbstractSettings::setCommand(const QString &command)
{
if (command == m_command)
return;
m_command = command;
}
QStringList AbstractSettings::options()
{
if (m_options.isEmpty())
readDocumentation();
return m_options.keys();
}
QString AbstractSettings::documentation(const QString &option) const
{
const int index = m_options.value(option, -1);
if (index != -1)
return m_docu.at(index);
else
return QString();
}
void AbstractSettings::save()
{
// Save settings, except styles
QSettings *s = Core::ICore::settings();
s->beginGroup(QLatin1String(Constants::SETTINGS_GROUP));
s->beginGroup(m_name);
QMap<QString, QVariant>::const_iterator iSettings = m_settings.constBegin();
while (iSettings != m_settings.constEnd()) {
s->setValue(iSettings.key(), iSettings.value());
++iSettings;
}
s->setValue(QLatin1String("command"), m_command);
s->endGroup();
s->endGroup();
// Save styles
if (m_stylesToRemove.isEmpty() && m_styles.isEmpty())
return;
if (!m_styleDir.exists()) {
const QString path = m_styleDir.absolutePath();
if (!(m_styleDir.mkpath(path)
&& m_styleDir.cd(path))) {
BeautifierPlugin::showError(tr("Failed to save styles. %1 does not exist.").arg(path));
return;
}
}
// remove old files
foreach (const QString file, m_stylesToRemove)
m_styleDir.remove(file);
m_stylesToRemove.clear();
QMap<QString, QString>::const_iterator iStyles = m_styles.constBegin();
while (iStyles != m_styles.constEnd()) {
// Only save changed styles.
if (!m_changedStyles.contains(iStyles.key())) {
++iStyles;
continue;
}
Utils::FileSaver saver(styleFileName(iStyles.key()));
if (saver.hasError()) {
BeautifierPlugin::showError(tr("Could not open file \"%1\": %2.")
.arg(saver.fileName())
.arg(saver.errorString()));
} else {
saver.write(iStyles.value().toLocal8Bit());
if (!saver.finalize()) {
BeautifierPlugin::showError(tr("Error while saving file \"%1\": %2.")
.arg(saver.fileName())
.arg(saver.errorString()));
}
}
++iStyles;
}
m_changedStyles.clear();
}
void AbstractSettings::createDocumentationFile() const
{
// Could be reimplemented to create a documentation file.
}
void AbstractSettings::read()
{
// Read settings, except styles
QSettings *s = Core::ICore::settings();
s->beginGroup(QLatin1String(Constants::SETTINGS_GROUP));
s->beginGroup(m_name);
const QStringList keys = s->allKeys();
foreach (const QString key, keys) {
if (key == QLatin1String("command"))
setCommand(s->value(key).toString());
else if (m_settings.contains(key))
m_settings[key] = s->value(key);
else
s->remove(key);
}
s->endGroup();
s->endGroup();
// Read styles
if (!m_styleDir.exists())
return;
m_stylesToRemove.clear();
m_styles.clear();
const QStringList files
= m_styleDir.entryList(QStringList() << QLatin1Char('*') + m_ending,
QDir::Files | QDir::Readable | QDir::NoDotAndDotDot);
foreach (const QString filename, files) {
// do not allow empty file names
if (filename == m_ending)
continue;
QFile file(m_styleDir.absoluteFilePath(filename));
if (file.open(QIODevice::ReadOnly)) {
m_styles.insert(filename.left(filename.length() - m_ending.length()),
QString::fromLocal8Bit(file.readAll()));
}
}
m_changedStyles.clear();
m_stylesToRemove.clear();
}
void AbstractSettings::readDocumentation()
{
const QString filename = documentationFilePath();
if (filename.isEmpty()) {
BeautifierPlugin::showError(tr("No documentation file specified."));
return;
}
QFile file(filename);
if (!file.exists())
createDocumentationFile();
if (!file.open(QIODevice::ReadOnly)) {
BeautifierPlugin::showError(tr("Could not open documentation file \"%1\".").arg(filename));
return;
}
QDomDocument xml;
xml.setContent(&file);
file.close();
QDomElement docElem = xml.documentElement();
if (docElem.tagName() != QLatin1String(Constants::DOCUMENTATION_XMLROOT)) {
BeautifierPlugin::showError(tr("The file \"%1\" is not a valid documentation file.")
.arg(filename));
return;
}
// We do not use QHash<QString, QString> since e.g. in Artistic Style there are many different
// keys with the same (long) documentation text. By splitting the keys from the documentation
// text we save a huge amount of memory.
m_options.clear();
m_docu.clear();
QDomElement e = docElem.firstChildElement(QLatin1String(Constants::DOCUMENTATION_XMLENTRY));
while (!e.isNull()) {
QDomElement keys = e.firstChildElement(QLatin1String(Constants::DOCUMENTATION_XMLKEYS));
if (keys.isNull()) {
m_docu << e.firstChildElement(QLatin1String(Constants::DOCUMENTATION_XMLDOC)).text();
m_options.insert(
e.firstChildElement(QLatin1String(Constants::DOCUMENTATION_XMLKEY)).text(),
m_docu.size() - 1);
} else {
m_docu << e.firstChildElement(QLatin1String(Constants::DOCUMENTATION_XMLDOC)).text();
const int index = m_docu.size() - 1;
QDomElement key = keys.firstChildElement(QLatin1String(Constants::DOCUMENTATION_XMLKEY));
while (!key.isNull()) {
m_options.insert(key.text(), index);
key = key.nextSiblingElement(QLatin1String(Constants::DOCUMENTATION_XMLKEY));
}
}
e = e.nextSiblingElement(QLatin1String(Constants::DOCUMENTATION_XMLENTRY));
}
}
} // namespace Internal
} // namespace Beautifier
/**************************************************************************
**
** Copyright (c) 2014 Lorenz Haas
** Contact: http://www.qt-project.org/legal
**
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef BEAUTIFIER_ABSTRACTSETTINGS_H
#define BEAUTIFIER_ABSTRACTSETTINGS_H
#include <QCoreApplication>
#include <QDir>
#include <QHash>
#include <QMap>
#include <QSet>
#include <QString>
#include <QStringList>
namespace Beautifier {
namespace Internal {
class AbstractSettings
{
Q_DECLARE_TR_FUNCTIONS(AbstractSettings)
public:
explicit AbstractSettings(const QString &name, const QString &ending);
virtual ~AbstractSettings();
void read();
void save();
virtual QString documentationFilePath() const = 0;
virtual void createDocumentationFile() const;
virtual QStringList completerWords();
QStringList styles() const;
QString style(const QString &key) const;
bool styleExists(const QString &key) const;
bool styleIsReadOnly(const QString &key);
void setStyle(const QString &key, const QString &value);
void removeStyle(const QString &key);
void replaceStyle(const QString &oldKey, const QString &newKey, const QString &value);
QString styleFileName(const QString &key) const;
QString command() const;
void setCommand(const QString &command);
QStringList options();
QString documentation(const QString &option) const;
protected:
QMap<QString, QString> m_styles;
QMap<QString, QVariant> m_settings;
void readDocumentation();
private:
QString m_name;
QString m_ending;
QDir m_styleDir;
QStringList m_stylesToRemove;
QSet<QString> m_changedStyles;
QString m_command;
QHash<QString, int> m_options;
QStringList m_docu;
};
} // namespace Internal
} // namespace Beautifier
#endif // BEAUTIFIER_ABSTRACTSETTINGS_H
/**************************************************************************
**
** Copyright (c) 2014 Lorenz Haas
** Contact: http://www.qt-project.org/legal
**
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "artisticstyle.h"
#include "artisticstyleconstants.h"
#include "artisticstyleoptionspage.h"
#include "artisticstylesettings.h"
#include "../beautifierconstants.h"
#include "../beautifierplugin.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <cppeditor/cppeditorconstants.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/project.h>
#include <utils/fileutils.h>
#include <QAction>
#include <QMenu>
namespace Beautifier {
namespace Internal {
namespace ArtisticStyle {
ArtisticStyle::ArtisticStyle(QObject *parent) :
BeautifierAbstractTool(parent),
m_settings(new ArtisticStyleSettings)
{
}
ArtisticStyle::~ArtisticStyle()
{
delete m_settings;
}
bool ArtisticStyle::initialize()
{
Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::ArtisticStyle::MENU_ID);
menu->menu()->setTitle(QLatin1String("Artistic Style"));
m_formatFile = new QAction(tr("Format Current File"), this);
Core::Command *cmd
= Core::ActionManager::registerAction(m_formatFile,
Constants::ArtisticStyle::ACTION_FORMATFILE,
Core::Context(Core::Constants::C_GLOBAL));
menu->addAction(cmd);
connect(m_formatFile, SIGNAL(triggered()), this, SLOT(formatFile()));
Core::ActionManager::actionContainer(Constants::MENU_ID)->addMenu(menu);
return true;
}
void ArtisticStyle::updateActions(Core::IEditor *editor)
{
m_formatFile->setEnabled(editor && editor->id() == CppEditor::Constants::CPPEDITOR_ID);
}
QList<QObject *> ArtisticStyle::autoReleaseObjects()
{
ArtisticStyleOptionsPage *optionsPage = new ArtisticStyleOptionsPage(m_settings, this);
return QList<QObject *>() << optionsPage;
}
void ArtisticStyle::formatFile()
{
QString cfgFileName;
if (m_settings->useOtherFiles()) {
if (const ProjectExplorer::Project *project
= ProjectExplorer::ProjectExplorerPlugin::currentProject()) {
const QStringList files = project->files(ProjectExplorer::Project::AllFiles);
for (int i = 0, total = files.size(); i < total; ++i) {
const QString &file = files.at(i);
if (!file.endsWith(QLatin1String(".astylerc")))
continue;
const QFileInfo fi(file);
if (fi.isReadable()) {
cfgFileName = file;
break;
}
}
}
}
if (cfgFileName.isEmpty() && m_settings->useHomeFile()) {
QString file = QDir::home().filePath(QLatin1String(".astylerc"));
if (QFile::exists(file)) {
cfgFileName = file;
} else {
file = QDir::home().filePath(QLatin1String("astylerc"));
if (QFile::exists(file))
cfgFileName = file;
}
}
if (m_settings->useCustomStyle())
cfgFileName = m_settings->styleFileName(m_settings->customStyle());
if (cfgFileName.isEmpty()) {
BeautifierPlugin::showError(tr("Could not get configuration file for Artistic Style."));
} else {
BeautifierPlugin::formatCurrentFile(QStringList()
<< m_settings->command()
<< QLatin1String("-q")
<< QLatin1String("--options=") + cfgFileName
<< QLatin1String("%file"));
}
}
} // namespace ArtisticStyle
} // namespace Internal
} // namespace Beautifier
/**************************************************************************
**
** Copyright (c) 2014 Lorenz Haas
** Contact: http://www.qt-project.org/legal
**
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef BEAUTIFIER_ARTISTICSTYLE_H
#define BEAUTIFIER_ARTISTICSTYLE_H