Commit 2219248d authored by Igor Kushnir's avatar Igor Kushnir
Browse files

Refactor formatterForUrl() into fileFormatter()

This refactoring is based on Daniel Mensinger's idea detailed in
!45 (comment 12455)

The added class FileFormatter gets rid of repeated parsing of formatter
and style config entries. The added interface IFileFormatter simplifies
non-source-formating code that used
ISourceFormatterController::formatterForUrl(); such code no longer
depends on ISourceFormatter.

Other improvements:
* Reduce the number of QMimeDatabase::mimeTypeForUrl() calls.
* Improve SourceFormatterController's documentation.
* Deduplicate SourceFormatterController's config-related code.
* Optimize the types of constants in Strings namespace.
* Don't waste time parsing formatter configuration if formatPolicy
  equals DocumentChangeSet::NoAutoFormat.
parent 5201edc2
Pipeline #260992 passed with stage
in 30 minutes and 48 seconds
......@@ -147,16 +147,6 @@ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatter
*/
virtual QString usageHint() const;
/** Formats using the current style.
* @param text The text to format
* @param url The URL to which the text belongs (its contents must not be changed).
* @param leftContext The context at the left side of the text. If it is in another line, it must end with a newline.
* @param rightContext The context at the right side of the text. If it is in the next line, it must start with a newline.
*
* If the source-formatter cannot work correctly with the context, it will just return the input text.
*/
virtual QString formatSource(const QString &text, const QUrl& url, const QMimeType &mime, const QString& leftContext = QString(), const QString& rightContext = QString()) const = 0;
/**
* Format with the given style, this is mostly for the kcm to format the preview text
* Its a bit of a hassle that this needs to be public API, but I can't find a better way
......@@ -203,9 +193,10 @@ class KDEVPLATFORMINTERFACES_EXPORT ISourceFormatter
int indentWidth = 0;
};
/** \return The indentation of the style applicable for the given url.
/** \return The indentation of @p style applicable for @p url and its MIME type @p mime
*/
virtual Indentation indentation(const QUrl& url) const = 0;
virtual Indentation indentation(const SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const = 0;
/** \return A string representing the map. Values are written in the form
* key=value and separated with ','.
......
......@@ -8,16 +8,37 @@
#ifndef KDEVPLATFORM_ISOURCEFORMATTERCONTROLLER_H
#define KDEVPLATFORM_ISOURCEFORMATTERCONTROLLER_H
#include "interfacesexport.h"
#include <QObject>
#include <QString>
#include "interfacesexport.h"
#include <memory>
class QUrl;
class QMimeType;
namespace KDevelop {
class ISourceFormatter;
class SourceFormatterStyle;
class IFileFormatter
{
Q_DISABLE_COPY_MOVE(IFileFormatter)
public:
IFileFormatter() = default;
virtual ~IFileFormatter() = default;
/**
* Format text using packaged source formatter and style.
* @param text the text to format
* @param leftContext the context at the left side of the text.
* If it is in another line, it must end with a newline.
* @param rightContext the context at the right side of the text.
* If it is in the next line, it must start with a newline.
*
* @note If the source formatter cannot work correctly with the context,
* it will just return the input text.
*/
virtual QString format(const QString& text, const QString& leftContext = QString(),
const QString& rightContext = QString()) const = 0;
};
/** \short An interface to the controller managing all source formatter plugins
*/
......@@ -29,26 +50,19 @@ public:
explicit ISourceFormatterController(QObject* parent = nullptr);
~ISourceFormatterController() override;
/** \return The formatter corresponding to the language
* of the document corresponding to the \p url.
* The language is then activated and the style is loaded.
* The source formatter is then ready to use.
* If mimetype of url is known already, use
* formatterForUrl(const QUrl& url, const QMimeType& mime) instead.
*/
virtual ISourceFormatter* formatterForUrl(const QUrl& url) = 0;
/** \return The formatter corresponding to the language
* of the document corresponding to the \p url.
* The language is then activated and the style is loaded.
* The source formatter is then ready to use.
* @param mime known mimetype of the url
using FileFormatterPtr = std::unique_ptr<IFileFormatter>;
/**
* Read user configuration for the given URL and package it into a file formatter object.
* @param url the URL of a document to be formatted
* @return the requested file formatter object or nullptr if no formatter is
* configured for @p url
*/
virtual ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) = 0;
virtual FileFormatterPtr fileFormatter(const QUrl& url) const = 0;
///\return @c true if there are formatters at all, @c false otherwise
virtual bool hasFormatters() const = 0;
virtual KDevelop::SourceFormatterStyle styleForUrl(const QUrl& url, const QMimeType& mime) = 0;
/**
* Disable source formatting
* Once disabled, source formatting cannot be reenabled. Call this from within tests.
......
......@@ -12,7 +12,6 @@
#include <algorithm>
#include <QStringList>
#include <QMimeDatabase>
#include <KLocalizedString>
......@@ -28,7 +27,6 @@
#include <language/editor/modificationrevisionset.h>
#include <interfaces/isourceformattercontroller.h>
#include <interfaces/isourceformatter.h>
#include <interfaces/iproject.h>
#include <interfaces/iprojectcontroller.h>
......@@ -382,12 +380,10 @@ DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const
//Create the actual new modified file
QStringList textLines = repr->text().split(QLatin1Char('\n'));
QUrl url = file.toUrl();
QMimeType mime = QMimeDatabase().mimeTypeForUrl(url);
auto core = ICore::self();
ISourceFormatter* formatter = core ? core->sourceFormatterController()->formatterForUrl(file.toUrl(), mime) : nullptr;
ISourceFormatterController::FileFormatterPtr formatter;
if (formatPolicy != DocumentChangeSet::NoAutoFormat) {
formatter = ICore::self()->sourceFormatterController()->fileFormatter(file.toUrl());
}
QVector<int> removedLines;
......@@ -404,10 +400,9 @@ DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const
QString rightContext = QStringList(textLines.mid(change.m_range.end().line())).join(QLatin1Char('\n')).mid(
change.m_range.end().column());
if (formatter && (formatPolicy == DocumentChangeSet::AutoFormatChanges
|| formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation)) {
if (formatter) {
QString oldNewText = change.m_newText;
change.m_newText = formatter->formatSource(change.m_newText, url, mime, leftContext, rightContext);
change.m_newText = formatter->format(change.m_newText, leftContext, rightContext);
if (formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation) {
// Reproduce the previous indentation
......
/*
SPDX-FileCopyrightText: 2009 Andreas Pakulat <apaku@gmx.de>
SPDX-FileCopyrightText: 2008 Cédric Pasteur <cedric.pasteur@free.fr>
SPDX-FileCopyrightText: 2021 Igor Kushnir <igorkuo@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
......@@ -11,6 +12,7 @@
#include <interfaces/isourceformattercontroller.h>
#include <interfaces/isourceformatter.h>
#include <QUrl>
#include <QVector>
#include <QMimeType>
......@@ -19,8 +21,6 @@
#include "shellexport.h"
class QUrl;
namespace KTextEditor {
class Document;
}
......@@ -77,15 +77,7 @@ public:
void initialize();
void cleanup();
//----------------- Public API defined in interfaces -------------------
/** \return The formatter corresponding to the language
* of the document corresponding to the \arg url.
*/
ISourceFormatter* formatterForUrl(const QUrl &url) override;
/** Loads and returns a source formatter for this mime type.
* The language is then activated and the style is loaded.
* The source formatter is then ready to use on a file.
*/
ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) override;
FileFormatterPtr fileFormatter(const QUrl& url) const override;
bool hasFormatters() const override;
/**
......@@ -98,9 +90,6 @@ public:
KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent);
KDevelop::SourceFormatterStyle styleForUrl(const QUrl& url, const QMimeType& mime) override;
KConfigGroup configForUrl(const QUrl& url) const;
KConfigGroup sessionConfig() const;
KConfigGroup globalConfig() const;
......@@ -121,23 +110,71 @@ private Q_SLOTS:
void beautifyLine();
void formatFiles();
void documentLoaded(const QPointer<KDevelop::TextDocument>& doc);
void projectOpened(const KDevelop::IProject* project);
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.
*/
QString addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType&);
/** \return The name of kate indentation mode for the mime type.
* examples are cstyle, python, etc.
*/
QString indentationMode(const QMimeType& mime);
void formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime);
// Adapts the mode of the editor regarding indentation-style
void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter,
const QUrl& url, bool ignoreModeline = false);
class FileFormatter final : public IFileFormatter
{
public:
/**
* @param url URL of the file to be formatted
*/
explicit FileFormatter(QUrl url);
/**
* Read and store in this object user-configured formatter and style for our file.
* @param formatters loaded and available formatters
* @return @e true on success, otherwise @e false
* @note This function is the second and last initialization step after the constructor.
* Call it once. Do not call any other non-static member function (other than the
* destructor) before this function is called and succeeds.
*/
bool readFormatterAndStyle(const QVector<ISourceFormatter*>& formatters);
QString formatterCaption() const;
QString styleCaption() const;
QString format(const QString& text, const QString& leftContext = QString(),
const QString& rightContext = QString()) const override;
/**
* @return @p input with a modeline string corresponding to source formatter configuration for our file
* @note A modeline within @p input is adjusted or a new one is appended to @p input.
*/
QString addModeline(QString input) const;
/**
* Format the open document.
* @param doc our file's document
*/
void formatDocument(IDocument& doc) const;
/**
* Adapt the mode of the editor regarding indentation style.
*/
void adaptEditorIndentationMode(KTextEditor::Document* doc, bool ignoreModeline = false) const;
/**
* Adapt global formatting state to a newly opened project @p project.
*/
static void projectOpened(const IProject& project, const QVector<ISourceFormatter*>& formatters);
private:
explicit FileFormatter(QUrl&& url, QMimeType&& mimeType, const KConfigGroup& sourceFormatterConfig,
const ISourceFormatter* formatter, SourceFormatterStyle&& style);
QUrl m_url;
const QMimeType m_mimeType; ///< the MIME type of @a m_url
KConfigGroup m_sourceFormatterConfig; ///< is determined by @a m_url
/**
* The names of @a m_formatter and @a m_style are read from the entry of @a m_sourceFormatterConfig
* at key=@a m_mimeType.name(). @a m_formatter and @a m_style themselves are then formed based on
* @e SourceFormatterController's loaded formatters and on global style configuration.
*/
const ISourceFormatter* m_formatter = nullptr;
SourceFormatterStyle m_style;
};
void resetUi();
......
......@@ -12,7 +12,6 @@
#include <debug.h>
#include <QMimeDatabase>
#include <QTextStream>
#include <KIO/StoredTransferJob>
......@@ -21,7 +20,6 @@
#include <interfaces/icore.h>
#include <interfaces/iuicontroller.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/isourceformatter.h>
#include <sublime/message.h>
using namespace KDevelop;
......@@ -104,18 +102,17 @@ void SourceFormatterJob::setFiles(const QList<QUrl>& fileList)
void SourceFormatterJob::formatFile(const QUrl& url)
{
// check mimetype
QMimeType mime = QMimeDatabase().mimeTypeForUrl(url);
qCDebug(SHELL) << "Checking file " << url << " of mime type " << mime.name();
auto formatter = m_sourceFormatterController->formatterForUrl(url, mime);
if (!formatter) // unsupported mime type
return;
qCDebug(SHELL) << "Checking whether to format file" << url;
SourceFormatterController::FileFormatter ff(url);
if (!ff.readFormatterAndStyle(m_sourceFormatterController->formatters())) {
return; // unsupported MIME type or no configured formatter for it
}
// if the file is opened in the editor, format the text in the editor without saving it
auto doc = ICore::self()->documentController()->documentForUrl(url);
if (doc) {
qCDebug(SHELL) << "Processing file " << url << "opened in editor";
m_sourceFormatterController->formatDocument(doc, formatter, mime);
ff.formatDocument(*doc);
return;
}
......@@ -125,8 +122,8 @@ void SourceFormatterJob::formatFile(const QUrl& url)
if (getJob->exec()) {
// TODO: really fromLocal8Bit/toLocal8Bit? no encoding detection? added in b8062f736a2bf2eec098af531a7fda6ebcdc7cde
QString text = QString::fromLocal8Bit(getJob->data());
text = formatter->formatSource(text, url, mime);
text = m_sourceFormatterController->addModelineForCurrentLang(text, url, mime);
text = ff.format(text);
text = ff.addModeline(text);
auto putJob = KIO::storedPut(text.toLocal8Bit(), url, -1, KIO::Overwrite);
// see getJob
......
......@@ -7,17 +7,14 @@
#include "astyle_plugin.h"
#include <QMimeDatabase>
#include <KPluginFactory>
#include <interfaces/icore.h>
#include <interfaces/isourceformattercontroller.h>
#include "astyle_formatter.h"
#include "astyle_stringiterator.h"
#include "astyle_preferences.h"
#include <KLocalizedString>
#include <QMimeType>
#include <QUrl>
static const char formattingCxxSample[] =
......@@ -255,12 +252,6 @@ QString AStylePlugin::formatSourceWithStyle(const SourceFormatterStyle& style,
return m_formatter->formatSource(text, leftContext, rightContext);
}
QString AStylePlugin::formatSource(const QString& text, const QUrl &url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const
{
auto style = ICore::self()->sourceFormatterController()->styleForUrl(url, mime);
return formatSourceWithStyle(style, text, url, mime, leftContext, rightContext);
}
static SourceFormatterStyle createPredefinedStyle(const QString& name, const QString& caption = QString())
{
SourceFormatterStyle st = SourceFormatterStyle( name );
......@@ -322,10 +313,11 @@ QString AStylePlugin::previewText(const SourceFormatterStyle& /*style*/, const Q
formattingSample(lang);
}
AStylePlugin::Indentation AStylePlugin::indentation(const QUrl& url) const
AStylePlugin::Indentation AStylePlugin::indentation(const SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const
{
// Call formatSource first, to initialize the m_formatter data structures according to the URL
formatSource(QString(), url, QMimeDatabase().mimeTypeForUrl(url), QString(), QString());
// Call formatSourceWithStyle() first to initialize the m_formatter data structures according to the arguments.
formatSourceWithStyle(style, QString(), url, mime, QString(), QString());
Indentation ret;
......
......@@ -28,10 +28,6 @@ public:
QString caption() const override;
QString description() const override;
/** Formats using the current style.
*/
QString formatSource(const QString& text, const QUrl &url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const override;
/** \return A map of predefined styles (a key and a caption for each type)
*/
QVector<KDevelop::SourceFormatterStyle> predefinedStyles() const override;
......@@ -51,9 +47,8 @@ public:
*/
QString previewText(const KDevelop::SourceFormatterStyle& style, const QMimeType& mime) const override;
/** \return The indentation type of the currently selected style.
*/
Indentation indentation(const QUrl &url) const override;
Indentation indentation(const KDevelop::SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const override;
static QString formattingSample(AStylePreferences::Language lang);
static QString indentingSample(AStylePreferences::Language lang);
......
......@@ -7,11 +7,8 @@
#include "sourcemanipulation.h"
#include <QMimeDatabase>
#include <interfaces/icore.h>
#include <interfaces/isourceformattercontroller.h>
#include <interfaces/isourceformatter.h>
#include <language/codegen/coderepresentation.h>
......@@ -232,12 +229,9 @@ bool SourceCodeInsertion::insertFunctionDeclaration(KDevelop::Declaration* decla
int line = findInsertionPoint();
decl = QLatin1String("\n\n") + applySubScope(decl);
const auto url = declaration->url().toUrl();
QMimeDatabase db;
QMimeType mime = db.mimeTypeForUrl(url);
auto i = ICore::self()->sourceFormatterController()->formatterForUrl(url, mime);
if (i) {
decl = i->formatSource(decl, url, mime);
const auto formatter = ICore::self()->sourceFormatterController()->fileFormatter(declaration->url().toUrl());
if (formatter) {
decl = formatter->format(decl);
}
return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(line), QString(), decl));
......
......@@ -10,10 +10,8 @@
#include <KPluginFactory>
#include <QTextStream>
#include <QTemporaryFile>
#include <QMimeDatabase>
#include <KProcess>
#include <interfaces/icore.h>
#include <interfaces/isourceformattercontroller.h>
#include <interfaces/isourceformatter.h>
#include <QDir>
#include <QTimer>
......@@ -30,6 +28,8 @@
#include <util/path.h>
#include <debug.h>
#include <QMimeType>
#include <memory>
using namespace KDevelop;
......@@ -99,11 +99,8 @@ QString CustomScriptPlugin::usageHint() const
"to be installed. Otherwise, code will not be formatted.");
}
QString CustomScriptPlugin::formatSourceWithStyle(const SourceFormatterStyle& style,
const QString& text,
const QUrl& url,
const QMimeType& /*mime*/,
const QString& leftContext,
QString CustomScriptPlugin::formatSourceWithStyle(const SourceFormatterStyle& style, const QString& text,
const QUrl& url, const QMimeType& mime, const QString& leftContext,
const QString& rightContext) const
{
KProcess proc;
......@@ -196,7 +193,7 @@ QString CustomScriptPlugin::formatSourceWithStyle(const SourceFormatterStyle& st
if ((!leftContext.isEmpty() || !rightContext.isEmpty()) && (text.contains(QLatin1Char(' ')) || output.contains(QLatin1Char('\t')))) {
// If we have to do contex-matching with tabs, determine the correct tab-width so that the context
// can be matched correctly
Indentation indent = indentation(url);
const auto indent = indentation(style, url, mime);
if (indent.indentationTabWidth > 0) {
tabWidth = indent.indentationTabWidth;
}
......@@ -205,12 +202,6 @@ QString CustomScriptPlugin::formatSourceWithStyle(const SourceFormatterStyle& st
return KDevelop::extractFormattedTextFromContext(output, text, leftContext, rightContext, tabWidth);
}
QString CustomScriptPlugin::formatSource(const QString& text, const QUrl& url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const
{
auto style = KDevelop::ICore::self()->sourceFormatterController()->styleForUrl(url, mime);
return formatSourceWithStyle(style, text, url, mime, leftContext, rightContext);
}
namespace {
QVector<SourceFormatterStyle> stylesFromLanguagePlugins()
{
......@@ -430,7 +421,8 @@ QString CustomScriptPlugin::previewText(const SourceFormatterStyle& style, const
return formattingSample() + QLatin1String("\n\n") + indentingSample();
}
QStringList CustomScriptPlugin::computeIndentationFromSample(const QUrl& url) const
QStringList CustomScriptPlugin::computeIndentationFromSample(const SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const
{
QStringList ret;
......@@ -445,7 +437,7 @@ QStringList CustomScriptPlugin::computeIndentationFromSample(const QUrl& url) co
<< language.name();
return ret;
}
QString formattedSample = formatSource(sample, url, QMimeDatabase().mimeTypeForUrl(url), QString(), QString());
const QString formattedSample = formatSourceWithStyle(style, sample, url, mime, QString(), QString());
const QStringList lines = formattedSample.split(QLatin1Char('\n'));
for (const QString& line : lines) {
......@@ -468,10 +460,11 @@ QStringList CustomScriptPlugin::computeIndentationFromSample(const QUrl& url) co
return ret;
}
CustomScriptPlugin::Indentation CustomScriptPlugin::indentation(const QUrl& url) const
CustomScriptPlugin::Indentation CustomScriptPlugin::indentation(const SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const
{
Indentation ret;
QStringList indent = computeIndentationFromSample(url);
const QStringList indent = computeIndentationFromSample(style, url, mime);
if (indent.isEmpty()) {
qCDebug(CUSTOMSCRIPT) << "failed extracting a valid indentation from sample for url" << url;
return ret; // No valid indentation could be extracted
......
......@@ -32,10 +32,6 @@ public:
QString description() const override;
QString usageHint() const override;
/** Formats using the current style.
*/
QString formatSource(const QString& text, const QUrl& url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const override;
QString formatSourceWithStyle(const KDevelop::SourceFormatterStyle& style,
const QString& text,
const QUrl& url,
......@@ -55,12 +51,12 @@ public:
*/
QString previewText(const KDevelop::SourceFormatterStyle& style, const QMimeType& mime) const override;
/** \return The indentation of the currently selected style.
*/
Indentation indentation(const QUrl& url) const override;
Indentation indentation(const KDevelop::SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const override;
private:
QStringList computeIndentationFromSample(const QUrl& url) const;
QStringList computeIndentationFromSample(const KDevelop::SourceFormatterStyle& style, const QUrl& url,
const QMimeType& mime) const;
};
class CustomScriptPreferences
......
Supports Markdown
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