File indexing completed on 2024-06-09 05:44:31

0001 /*
0002     SPDX-FileCopyrightText: 2022 Waqar Ahmed <waqar.17a@gmail.com>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 #pragma once
0006 
0007 #include <QJsonObject>
0008 #include <QPointer>
0009 #include <QProcess>
0010 
0011 #include <KLocalizedString>
0012 #include <KTextEditor/Document>
0013 
0014 #include "FormatApply.h"
0015 #include <ktexteditor_utils.h>
0016 
0017 struct PatchLine;
0018 
0019 class AbstractFormatter : public QObject
0020 {
0021     Q_OBJECT
0022 public:
0023     AbstractFormatter(const QJsonObject &obj, KTextEditor::Document *parent)
0024         : QObject(parent)
0025         , originalText(parent->text())
0026         , m_doc(parent)
0027         , m_globalConfig(obj)
0028     {
0029     }
0030 
0031     virtual ~AbstractFormatter()
0032     {
0033         if (m_procHandle && m_procHandle->state() != QProcess::NotRunning) {
0034             m_procHandle->disconnect(this);
0035             m_procHandle->kill();
0036             m_procHandle->waitForFinished();
0037         }
0038     }
0039 
0040     virtual void run(KTextEditor::Document *doc);
0041 
0042     void setCursorPosition(KTextEditor::Cursor c)
0043     {
0044         m_pos = c;
0045     }
0046 
0047     bool formatOnSaveEnabled(bool defaultValue) const
0048     {
0049         return m_globalConfig.value(name()).toObject().value(QLatin1String("formatOnSave")).toBool(defaultValue);
0050     }
0051 
0052     QString cmdline() const
0053     {
0054         if (m_procHandle) {
0055             return m_procHandle->program() + QLatin1String(" ") + m_procHandle->arguments().join(QLatin1String(" "));
0056         }
0057         return {};
0058     }
0059 
0060     const QString originalText;
0061 
0062 protected:
0063     struct RunOutput {
0064         int exitCode = -1;
0065         QByteArray out;
0066         QByteArray err;
0067     };
0068 
0069     virtual void onResultReady(const RunOutput &o);
0070 
0071     virtual bool supportsStdin() const
0072     {
0073         return false;
0074     }
0075 
0076     virtual QStringList args(KTextEditor::Document *doc) const = 0;
0077     virtual QString name() const = 0;
0078     virtual QString workingDir() const
0079     {
0080         return {};
0081     }
0082 
0083     virtual QProcessEnvironment env()
0084     {
0085         return QProcessEnvironment::systemEnvironment();
0086     }
0087 
0088     QPointer<KTextEditor::Document> m_doc;
0089     QJsonObject m_config;
0090     QPointer<QProcess> m_procHandle;
0091     KTextEditor::Cursor m_pos;
0092 
0093 private:
0094     QByteArray textForStdin() const
0095     {
0096         return originalText.toUtf8();
0097     }
0098 
0099     const QJsonObject m_globalConfig;
0100 
0101 Q_SIGNALS:
0102     void textFormatted(AbstractFormatter *formatter, KTextEditor::Document *doc, const QByteArray &text, int offset = -1);
0103     void textFormattedPatch(KTextEditor::Document *doc, const std::vector<PatchLine> &);
0104     void error(const QString &error);
0105 };
0106 
0107 class ClangFormat : public AbstractFormatter
0108 {
0109 public:
0110     using AbstractFormatter::AbstractFormatter;
0111     QString name() const override
0112     {
0113         return QStringLiteral("clang-format");
0114     }
0115 
0116     QStringList args(KTextEditor::Document *doc) const override;
0117     void onResultReady(const RunOutput &o) override;
0118 
0119     bool supportsStdin() const override
0120     {
0121         return true;
0122     }
0123 
0124 private:
0125     bool m_withCursor = false;
0126 };
0127 
0128 class DartFormat : public AbstractFormatter
0129 {
0130 public:
0131     using AbstractFormatter::AbstractFormatter;
0132     QString name() const override
0133     {
0134         return QStringLiteral("dart");
0135     }
0136 
0137     QStringList args(KTextEditor::Document *doc) const override
0138     {
0139         return {QStringLiteral("format"),
0140                 QStringLiteral("--output=show"),
0141                 QStringLiteral("--summary=none"),
0142                 QStringLiteral("--set-exit-if-changed"),
0143                 doc->url().toDisplayString(QUrl::PreferLocalFile)};
0144     }
0145 
0146 private:
0147     void onResultReady(const RunOutput &out) override;
0148 };
0149 
0150 class JsonJqFormat : public AbstractFormatter
0151 {
0152 public:
0153     using AbstractFormatter::AbstractFormatter;
0154     QString name() const override
0155     {
0156         return QStringLiteral("jq");
0157     }
0158 
0159     QStringList args(KTextEditor::Document *doc) const override
0160     {
0161         // Reuse doc's indent
0162         bool ok = false;
0163         int width = doc->configValue(QStringLiteral("indent-width")).toInt(&ok);
0164         if (!ok) {
0165             width = 4;
0166         }
0167         return {
0168             QStringLiteral("."),
0169             QStringLiteral("--indent"),
0170             QString::number(width),
0171             QStringLiteral("-M"), // no color
0172         };
0173     }
0174 
0175 private:
0176     bool supportsStdin() const override
0177     {
0178         return true;
0179     }
0180 };
0181 
0182 class PrettierFormat : public AbstractFormatter
0183 {
0184 public:
0185     using AbstractFormatter::AbstractFormatter;
0186 
0187     QString name() const override
0188     {
0189         return QStringLiteral("prettier");
0190     }
0191 
0192     void run(KTextEditor::Document *) override;
0193 
0194     QStringList args(KTextEditor::Document *doc) const override
0195     {
0196         return {QStringLiteral("--no-color"), doc->url().toDisplayString(QUrl::PreferLocalFile)};
0197     }
0198 
0199     QString workingDir() const override
0200     {
0201         return Utils::projectBaseDirForDocument(m_doc);
0202     }
0203 
0204 private:
0205     void onReadyReadOut();
0206     void onReadyReadErr();
0207 
0208     void setupNode();
0209     static inline QPointer<QTemporaryFile> s_tempFile = nullptr;
0210     static inline QPointer<QProcess> s_nodeProcess = nullptr;
0211     RunOutput m_runOutput;
0212 };
0213 
0214 class RustFormat : public AbstractFormatter
0215 {
0216 public:
0217     using AbstractFormatter::AbstractFormatter;
0218     QString name() const override
0219     {
0220         return QStringLiteral("rustfmt");
0221     }
0222 
0223     QStringList args(KTextEditor::Document *) const override
0224     {
0225         return {QStringLiteral("--color=never"), QStringLiteral("--emit=stdout")};
0226     }
0227 
0228 private:
0229     bool supportsStdin() const override
0230     {
0231         return true;
0232     }
0233 
0234     void onResultReady(const RunOutput &out) override;
0235 };
0236 
0237 class XmlLintFormat : public AbstractFormatter
0238 {
0239 public:
0240     using AbstractFormatter::AbstractFormatter;
0241     QString name() const override
0242     {
0243         return QStringLiteral("xmllint");
0244     }
0245 
0246     QStringList args(KTextEditor::Document *) const override
0247     {
0248         return {QStringLiteral("--format"), QStringLiteral("-")};
0249     }
0250 
0251     QProcessEnvironment env() override
0252     {
0253         auto environment = AbstractFormatter::env();
0254         auto ciface = m_doc;
0255 
0256         // Reuse doc's indent
0257         bool ok = false;
0258         int width = ciface->configValue(QStringLiteral("indent-width")).toInt(&ok);
0259         if (!ok) {
0260             return environment;
0261         }
0262         bool spaces = ciface->configValue(QStringLiteral("replace-tabs")).toBool();
0263         QString indent;
0264         if (spaces) {
0265             indent = QString(width, QLatin1Char(' '));
0266         } else {
0267             indent = QStringLiteral("\t");
0268         }
0269 
0270         environment.insert(QStringLiteral("XMLLINT_INDENT"), indent);
0271         return environment;
0272     }
0273 
0274     bool supportsStdin() const override
0275     {
0276         return true;
0277     }
0278 };
0279 
0280 class GoFormat : public AbstractFormatter
0281 {
0282 public:
0283     using AbstractFormatter::AbstractFormatter;
0284     QString name() const override
0285     {
0286         return QStringLiteral("gofmt");
0287     }
0288 
0289     QStringList args(KTextEditor::Document *) const override
0290     {
0291         return {QStringLiteral("-d")};
0292     }
0293 
0294 private:
0295     bool supportsStdin() const override
0296     {
0297         return true;
0298     }
0299 
0300     void onResultReady(const RunOutput &out) override;
0301 };
0302 
0303 class ZigFormat : public AbstractFormatter
0304 {
0305 public:
0306     using AbstractFormatter::AbstractFormatter;
0307     QString name() const override
0308     {
0309         return QStringLiteral("zig");
0310     }
0311 
0312     QStringList args(KTextEditor::Document *) const override
0313     {
0314         return {QStringLiteral("fmt"), QStringLiteral("--stdin")};
0315     }
0316 
0317 private:
0318     bool supportsStdin() const override
0319     {
0320         return true;
0321     }
0322 };
0323 
0324 class CMakeFormat : public AbstractFormatter
0325 {
0326 public:
0327     using AbstractFormatter::AbstractFormatter;
0328     QString name() const override
0329     {
0330         return QStringLiteral("cmake-format");
0331     }
0332 
0333     QStringList args(KTextEditor::Document *) const override
0334     {
0335         return {m_doc->url().toLocalFile()};
0336     }
0337 };
0338 
0339 class AutoPep8Format : public AbstractFormatter
0340 {
0341 public:
0342     using AbstractFormatter::AbstractFormatter;
0343     QString name() const override
0344     {
0345         return QStringLiteral("autopep8");
0346     }
0347 
0348     QStringList args(KTextEditor::Document *) const override
0349     {
0350         return {m_doc->url().toLocalFile()};
0351     }
0352 };