customscript_plugin.cpp 21 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* This file is part of KDevelop
   Copyright (C) 2008 Cédric Pasteur <cedric.pasteur@free.fr>
   Copyright (C) 2011 David Nolden <david.nolden.kdevelop@art-master.de>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
 */

#include "customscript_plugin.h"

#include <KPluginFactory>
#include <QTextStream>
#include <QTemporaryFile>
26
#include <QMimeDatabase>
27
28
29
#include <KProcess>
#include <interfaces/icore.h>
#include <interfaces/isourceformattercontroller.h>
30
#include <interfaces/isourceformatter.h>
31
32
#include <memory>
#include <QDir>
33
#include <QTimer>
Dāvis Mosāns's avatar
Dāvis Mosāns committed
34

35
36
37
38
39
40
#include <util/formattinghelpers.h>
#include <interfaces/iprojectcontroller.h>
#include <interfaces/iproject.h>
#include <KMessageBox>
#include <interfaces/iuicontroller.h>
#include <KParts/MainWindow>
41
#include <KLocalizedString>
42
43
#include <interfaces/ilanguagecontroller.h>
#include <language/interfaces/ilanguagesupport.h>
44
#include <util/path.h>
45
#include <debug.h>
46
47
48

using namespace KDevelop;

49
static QPointer<CustomScriptPlugin> indentPluginSingleton;
50

Kevin Funk's avatar
Kevin Funk committed
51
K_PLUGIN_FACTORY_WITH_JSON(CustomScriptFactory, "kdevcustomscript.json", registerPlugin<CustomScriptPlugin>(); )
52
53

// Replaces ${KEY} in command with variables[KEY]
54
static QString replaceVariables(QString command, const QMap<QString, QString>& variables)
55
{
56
57
    while (command.contains(QLatin1String("${"))) {
        int pos = command.indexOf(QLatin1String("${"));
58
        int end = command.indexOf(QLatin1Char('}'), pos + 2);
Kevin Funk's avatar
Kevin Funk committed
59
60
61
62
63
        if (end == -1) {
            break;
        }
        QString key = command.mid(pos + 2, end - pos - 2);

64
65
66
        const auto variableIt = variables.constFind(key);
        if (variableIt != variables.constEnd()) {
            command.replace(pos, 1 + end - pos, *variableIt );
Kevin Funk's avatar
Kevin Funk committed
67
68
        } else {
            qCDebug(CUSTOMSCRIPT) << "found no variable while replacing in shell-command" << command << "key" << key << "available:" << variables;
69
            command.remove(pos, 1 + end - pos);
Kevin Funk's avatar
Kevin Funk committed
70
71
72
        }
    }
    return command;
73
74
}

Kevin Funk's avatar
Kevin Funk committed
75
CustomScriptPlugin::CustomScriptPlugin(QObject* parent, const QVariantList&)
76
    : IPlugin(QStringLiteral("kdevcustomscript"), parent)
77
{
Kevin Funk's avatar
Kevin Funk committed
78
79
    m_currentStyle = predefinedStyles().at(0);
    indentPluginSingleton = this;
80
81
82
83
84
85
}

CustomScriptPlugin::~CustomScriptPlugin()
{
}

86
QString CustomScriptPlugin::name() const
87
{
Kevin Funk's avatar
Kevin Funk committed
88
    // This needs to match the X-KDE-PluginInfo-Name entry from the .desktop file!
89
    return QStringLiteral("kdevcustomscript");
90
91
}

92
QString CustomScriptPlugin::caption() const
93
{
94
    return QStringLiteral("Custom Script Formatter");
95
96
}

97
QString CustomScriptPlugin::description() const
98
{
Kevin Funk's avatar
Kevin Funk committed
99
100
101
102
103
104
105
    return i18n("<b>Indent and Format Source Code.</b><br />"
                "This plugin allows using powerful external formatting tools "
                "that can be invoked through the command-line.<br />"
                "For example, the <b>uncrustify</b>, <b>astyle</b> or <b>indent</b> "
                "formatters can be used.<br />"
                "The advantage of command-line formatters is that formatting configurations "
                "can be easily shared by all team members, independent of their preferred IDE.");
106
107
}

108
QString CustomScriptPlugin::formatSourceWithStyle(SourceFormatterStyle style, const QString& text, const QUrl& url, const QMimeType& /*mime*/, const QString& leftContext, const QString& rightContext) const
109
{
Kevin Funk's avatar
Kevin Funk committed
110
111
112
113
114
115
116
117
    KProcess proc;
    QTextStream ios(&proc);

    std::unique_ptr<QTemporaryFile> tmpFile;

    if (style.content().isEmpty()) {
        style = predefinedStyle(style.name());
        if (style.content().isEmpty()) {
118
            qCWarning(CUSTOMSCRIPT) << "Empty contents for style" << style.name() << "for indent plugin";
Kevin Funk's avatar
Kevin Funk committed
119
120
121
122
123
124
125
126
            return text;
        }
    }

    QString useText = text;
    useText = leftContext + useText + rightContext;

    QMap<QString, QString> projectVariables;
127
128
    const auto projects = ICore::self()->projectController()->projects();
    for (IProject* project : projects) {
Kevin Funk's avatar
Kevin Funk committed
129
130
131
132
133
134
135
        projectVariables[project->name()] = project->path().toUrl().toLocalFile();
    }

    QString command = style.content();

    // Replace ${Project} with the project path
    command = replaceVariables(command, projectVariables);
136
    command.replace(QLatin1String("$FILE"), url.toLocalFile());
Kevin Funk's avatar
Kevin Funk committed
137

138
    if (command.contains(QLatin1String("$TMPFILE"))) {
139
        tmpFile.reset(new QTemporaryFile(QDir::tempPath() + QLatin1String("/code")));
Kevin Funk's avatar
Kevin Funk committed
140
141
142
        tmpFile->setAutoRemove(false);
        if (tmpFile->open()) {
            qCDebug(CUSTOMSCRIPT) << "using temporary file" << tmpFile->fileName();
143
            command.replace(QLatin1String("$TMPFILE"), tmpFile->fileName());
Kevin Funk's avatar
Kevin Funk committed
144
145
            QByteArray useTextArray = useText.toLocal8Bit();
            if (tmpFile->write(useTextArray) != useTextArray.size()) {
146
                qCWarning(CUSTOMSCRIPT) << "failed to write text to temporary file";
Kevin Funk's avatar
Kevin Funk committed
147
148
149
                return text;
            }
        } else {
150
            qCWarning(CUSTOMSCRIPT) << "Failed to create a temporary file";
Kevin Funk's avatar
Kevin Funk committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
            return text;
        }
        tmpFile->close();
    }

    qCDebug(CUSTOMSCRIPT) << "using shell command for indentation: " << command;
    proc.setShellCommand(command);
    proc.setOutputChannelMode(KProcess::OnlyStdoutChannel);

    proc.start();
    if (!proc.waitForStarted()) {
        qCDebug(CUSTOMSCRIPT) << "Unable to start indent" << endl;
        return text;
    }

    if (!tmpFile.get()) {
        proc.write(useText.toLocal8Bit());
    }

    proc.closeWriteChannel();
    if (!proc.waitForFinished()) {
        qCDebug(CUSTOMSCRIPT) << "Process doesn't finish" << endl;
        return text;
    }

    QString output;

    if (tmpFile.get()) {
        QFile f(tmpFile->fileName());
        if (f.open(QIODevice::ReadOnly)) {
            output = QString::fromLocal8Bit(f.readAll());
        } else {
183
            qCWarning(CUSTOMSCRIPT) << "Failed opening the temporary file for reading";
Kevin Funk's avatar
Kevin Funk committed
184
185
186
187
188
189
            return text;
        }
    } else {
        output = ios.readAll();
    }
    if (output.isEmpty()) {
190
        qCWarning(CUSTOMSCRIPT) << "indent returned empty text for style" << style.name() << style.content();
Kevin Funk's avatar
Kevin Funk committed
191
192
193
194
        return text;
    }

    int tabWidth = 4;
195
    if ((!leftContext.isEmpty() || !rightContext.isEmpty()) && (text.contains(QLatin1Char('	')) || output.contains(QLatin1Char('\t')))) {
Kevin Funk's avatar
Kevin Funk committed
196
197
198
199
200
201
202
        // 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);
        if (indent.indentationTabWidth > 0) {
            tabWidth = indent.indentationTabWidth;
        }
    }
203
204

    return KDevelop::extractFormattedTextFromContext(output, text, leftContext, rightContext, tabWidth);
205
206
}

207
QString CustomScriptPlugin::formatSource(const QString& text, const QUrl& url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const
208
{
209
210
	auto style = KDevelop::ICore::self()->sourceFormatterController()->styleForUrl(url, mime);
    return formatSourceWithStyle(style, text, url, mime, leftContext, rightContext);
211
212
}

213
static QVector<SourceFormatterStyle> stylesFromLanguagePlugins()
Kevin Funk's avatar
Kevin Funk committed
214
{
215
    QVector<KDevelop::SourceFormatterStyle> styles;
216
217
    const auto loadedLanguages = ICore::self()->languageController()->loadedLanguages();
    for (auto* lang : loadedLanguages) {
Kevin Funk's avatar
Kevin Funk committed
218
219
        const SourceFormatterItemList& languageStyles = lang->sourceFormatterItems();
        for (const SourceFormatterStyleItem& item: languageStyles) {
220
            if (item.engine == QLatin1String("customscript")) {
Kevin Funk's avatar
Kevin Funk committed
221
222
223
224
225
226
                styles << item.style;
            }
        }
    }

    return styles;
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
227
}
228

229
KDevelop::SourceFormatterStyle CustomScriptPlugin::predefinedStyle(const QString& name) const
230
{
231
232
	const auto& langStyles = stylesFromLanguagePlugins();
    for (auto& langStyle: langStyles) {
Kevin Funk's avatar
Kevin Funk committed
233
234
235
236
237
238
239
        qCDebug(CUSTOMSCRIPT) << "looking at style from language with custom sample" << langStyle.description() << langStyle.overrideSample();
        if (langStyle.name() == name) {
            return langStyle;
        }
    }

    SourceFormatterStyle result(name);
240
    if (name == QLatin1String("GNU_indent_GNU")) {
Kevin Funk's avatar
Kevin Funk committed
241
        result.setCaption(i18n("Gnu Indent: GNU"));
242
        result.setContent(QStringLiteral("indent"));
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
243
        result.setUsePreview(true);
244
    } else if (name == QLatin1String("GNU_indent_KR")) {
Kevin Funk's avatar
Kevin Funk committed
245
        result.setCaption(i18n("Gnu Indent: Kernighan & Ritchie"));
246
        result.setContent(QStringLiteral("indent -kr"));
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
247
        result.setUsePreview(true);
248
    } else if (name == QLatin1String("GNU_indent_orig")) {
Kevin Funk's avatar
Kevin Funk committed
249
        result.setCaption(i18n("Gnu Indent: Original Berkeley indent style"));
250
        result.setContent(QStringLiteral("indent -orig"));
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
251
        result.setUsePreview(true);
252
253
254
    } else if (name == QLatin1String("kdev_format_source")) {
        result.setCaption(QStringLiteral("KDevelop: kdev_format_source"));
        result.setContent(QStringLiteral("kdev_format_source $FILE $TMPFILE"));
Kevin Funk's avatar
Kevin Funk committed
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
        result.setUsePreview(false);
        result.setDescription(i18n("Description:<br />"
                                   "<b>kdev_format_source</b> is a script bundled with KDevelop "
                                   "which allows using fine-grained formatting rules by placing "
                                   "meta-files called <b>format_sources</b> into the file-system.<br /><br />"
                                   "Each line of the <b>format_sources</b> files defines a list of wildcards "
                                   "followed by a colon and the used formatting-command.<br /><br />"
                                   "The formatting-command should use <b>$TMPFILE</b> to reference the "
                                   "temporary file to reformat.<br /><br />"
                                   "Example:<br />"
                                   "<b>*.cpp *.h : myformatter $TMPFILE</b><br />"
                                   "This will reformat all files ending with <b>.cpp</b> or <b>.h</b> using "
                                   "the custom formatting script <b>myformatter</b>.<br /><br />"
                                   "Example: <br />"
                                   "<b>subdir/* : uncrustify -l CPP -f $TMPFILE -c uncrustify.config -o $TMPFILE</b> <br />"
                                   "This will reformat all files in subdirectory <b>subdir</b> using the <b>uncrustify</b> "
                                   "tool with the config-file <b>uncrustify.config</b>."));
    }
    result.setMimeTypes({
274
275
276
277
278
        {QStringLiteral("text/x-c++src"), QStringLiteral("C++")},
        {QStringLiteral("text/x-chdr"),   QStringLiteral("C")},
        {QStringLiteral("text/x-c++hdr"), QStringLiteral("C++")},
        {QStringLiteral("text/x-csrc"),   QStringLiteral("C")},
        {QStringLiteral("text/x-java"),   QStringLiteral("Java")},
279
280
281
282
        {QStringLiteral("text/x-csharp"), QStringLiteral("C#")},
        {QStringLiteral("text/x-objcsrc"), QStringLiteral("Objective-C")},
        {QStringLiteral("text/x-objc++src"), QStringLiteral("Objective-C++")},
        {QStringLiteral("text/x-objchdr"), QStringLiteral("Objective-C")},
Kevin Funk's avatar
Kevin Funk committed
283
284
    });
    return result;
285
286
}

287
QVector<KDevelop::SourceFormatterStyle> CustomScriptPlugin::predefinedStyles() const
288
{
289
290
291
292
293
294
    const QVector<KDevelop::SourceFormatterStyle> styles = stylesFromLanguagePlugins() + QVector<KDevelop::SourceFormatterStyle>{
        predefinedStyle(QStringLiteral("kdev_format_source")),
        predefinedStyle(QStringLiteral("GNU_indent_GNU")),
        predefinedStyle(QStringLiteral("GNU_indent_KR")),
        predefinedStyle(QStringLiteral("GNU_indent_orig")),
    };
Kevin Funk's avatar
Kevin Funk committed
295
    return styles;
296
297
}

298
KDevelop::SettingsWidget* CustomScriptPlugin::editStyleWidget(const QMimeType& mime) const
299
{
Kevin Funk's avatar
Kevin Funk committed
300
301
    Q_UNUSED(mime);
    return new CustomScriptPreferences();
302
303
304
305
}

static QString formattingSample()
{
306
    return QStringLiteral(
Kevin Funk's avatar
Kevin Funk committed
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
        "// Formatting\n"
        "void func(){\n"
        "\tif(isFoo(a,b))\n"
        "\tbar(a,b);\n"
        "if(isFoo)\n"
        "\ta=bar((b-c)*a,*d--);\n"
        "if(  isFoo( a,b ) )\n"
        "\tbar(a, b);\n"
        "if (isFoo) {isFoo=false;cat << isFoo <<endl;}\n"
        "if(isFoo)DoBar();if (isFoo){\n"
        "\tbar();\n"
        "}\n"
        "\telse if(isBar()){\n"
        "\tannotherBar();\n"
        "}\n"
        "int var = 1;\n"
        "int *ptr = &var;\n"
        "int &ref = i;\n"
        "\n"
        "QList<int>::const_iterator it = list.begin();\n"
        "}\n"
        "namespace A {\n"
        "namespace B {\n"
        "void foo() {\n"
        "  if (true) {\n"
        "    func();\n"
        "  } else {\n"
        "    // bla\n"
        "  }\n"
        "}\n"
        "}\n"
338
        "}\n");
339
340
341
342
}

static QString indentingSample()
{
343
    return QStringLiteral(
Kevin Funk's avatar
Kevin Funk committed
344
345
346
347
348
349
350
351
352
353
354
355
356
        "// Indentation\n"
        "#define foobar(A)\\\n"
        "{Foo();Bar();}\n"
        "#define anotherFoo(B)\\\n"
        "return Bar()\n"
        "\n"
        "namespace Bar\n"
        "{\n"
        "class Foo\n"
        "{public:\n"
        "Foo();\n"
        "virtual ~Foo();\n"
        "};\n"
357
358
        "void bar(int foo)\n"
        "{\n"
Kevin Funk's avatar
Kevin Funk committed
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
        "switch (foo)\n"
        "{\n"
        "case 1:\n"
        "a+=1;\n"
        "break;\n"
        "case 2:\n"
        "{\n"
        "a += 2;\n"
        " break;\n"
        "}\n"
        "}\n"
        "if (isFoo)\n"
        "{\n"
        "bar();\n"
        "}\n"
        "else\n"
        "{\n"
        "anotherBar();\n"
        "}\n"
378
        "}\n"
Kevin Funk's avatar
Kevin Funk committed
379
380
381
        "int foo()\n"
        "\twhile(isFoo)\n"
        "\t\t{\n"
382
        "\t\t\t// ...\n"
Kevin Funk's avatar
Kevin Funk committed
383
        "\t\t\tgoto error;\n"
384
        "\t\t/* .... */\n"
Kevin Funk's avatar
Kevin Funk committed
385
        "\t\terror:\n"
386
        "\t\t\t//...\n"
Kevin Funk's avatar
Kevin Funk committed
387
388
389
390
391
392
393
        "\t\t}\n"
        "\t}\n"
        "fooArray[]={ red,\n"
        "\tgreen,\n"
        "\tdarkblue};\n"
        "fooFunction(barArg1,\n"
        "\tbarArg2,\n"
394
395
        "\tbarArg3);\n"
        );
396
397
}

398
QString CustomScriptPlugin::previewText(const SourceFormatterStyle& style, const QMimeType& /*mime*/) const
399
{
Kevin Funk's avatar
Kevin Funk committed
400
401
402
    if (!style.overrideSample().isEmpty()) {
        return style.overrideSample();
    }
403
    return formattingSample() + QLatin1String("\n\n") + indentingSample();
404
405
}

406
QStringList CustomScriptPlugin::computeIndentationFromSample(const QUrl& url) const
407
{
Kevin Funk's avatar
Kevin Funk committed
408
    QStringList ret;
409

Kevin Funk's avatar
Kevin Funk committed
410
411
    auto languages = ICore::self()->languageController()->languagesForUrl(url);
    if (languages.isEmpty()) {
412
        return ret;
Kevin Funk's avatar
Kevin Funk committed
413
    }
414

415
    QString sample = languages[0]->indentationSample();
416
    QString formattedSample = formatSource(sample, url, QMimeDatabase().mimeTypeForUrl(url), QString(), QString());
417

418
419
    const QStringList lines = formattedSample.split(QLatin1Char('\n'));
    for (const QString& line : lines) {
Kevin Funk's avatar
Kevin Funk committed
420
421
        if (!line.isEmpty() && line[0].isSpace()) {
            QString indent;
422
            for (const QChar c : line) {
Kevin Funk's avatar
Kevin Funk committed
423
424
425
426
427
428
429
430
431
432
433
434
435
436
                if (c.isSpace()) {
                    indent.push_back(c);
                } else {
                    break;
                }
            }

            if (!indent.isEmpty() && !ret.contains(indent)) {
                ret.push_back(indent);
            }
        }
    }

    return ret;
437
438
}

439
CustomScriptPlugin::Indentation CustomScriptPlugin::indentation(const QUrl& url) const
440
{
441
    Indentation ret;
Kevin Funk's avatar
Kevin Funk committed
442
443
    QStringList indent = computeIndentationFromSample(url);
    if (indent.isEmpty()) {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
444
        qCDebug(CUSTOMSCRIPT) << "failed extracting a valid indentation from sample for url" << url;
445
446
447
        return ret; // No valid indentation could be extracted
    }

448
449
    if (indent[0].contains(QLatin1Char(' '))) {
        ret.indentWidth = indent[0].count(QLatin1Char(' '));
Kevin Funk's avatar
Kevin Funk committed
450
451
    }

452
    if (!indent.join(QString()).contains(QLatin1Char('	'))) {
Kevin Funk's avatar
Kevin Funk committed
453
454
        ret.indentationTabWidth = -1;         // Tabs are not used for indentation
    }
455
    if (indent[0] == QLatin1String("	")) {
Kevin Funk's avatar
Kevin Funk committed
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
        // The script indents with tabs-only
        // The problem is that we don't know how
        // wide a tab is supposed to be.
        //
        // We need indentation-width=tab-width
        // to make the editor do tab-only formatting,
        // so choose a random with of 4.
        ret.indentWidth = 4;
        ret.indentationTabWidth = 4;
    } else if (ret.indentWidth)   {
        // Tabs are used for indentation, alongside with spaces
        // Try finding out how many spaces one tab stands for.
        // Do it by assuming a uniform indentation-step with each level.

        for (int pos = 0; pos < indent.size(); ++pos) {
471
            if (indent[pos] == QLatin1String("	")&& pos >= 1) {
Kevin Funk's avatar
Kevin Funk committed
472
473
474
475
476
477
478
479
480
481
482
483
484
                // This line consists of only a tab.
                int prevWidth = indent[pos - 1].length();
                int prevPrevWidth = (pos >= 2) ? indent[pos - 2].length() : 0;
                int step = prevWidth - prevPrevWidth;
                qCDebug(CUSTOMSCRIPT) << "found in line " << pos << prevWidth << prevPrevWidth << step;
                if (step > 0 && step <= prevWidth) {
                    qCDebug(CUSTOMSCRIPT) << "Done";
                    ret.indentationTabWidth = prevWidth + step;
                    break;
                }
            }
        }
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
485

486
    qCDebug(CUSTOMSCRIPT) << "indent-sample" << QLatin1Char('\"') + indent.join(QLatin1Char('\n')) + QLatin1Char('\"') << "extracted tab-width" << ret.indentationTabWidth << "extracted indentation width" << ret.indentWidth;
Dāvis Mosāns's avatar
Dāvis Mosāns committed
487

488
    return ret;
489
490
491
492
}

void CustomScriptPreferences::updateTimeout()
{
Kevin Funk's avatar
Kevin Funk committed
493
    const QString& text = indentPluginSingleton.data()->previewText(m_style, QMimeType());
Milian Wolff's avatar
Milian Wolff committed
494
    QString formatted = indentPluginSingleton.data()->formatSourceWithStyle(m_style, text, QUrl(), QMimeType());
Kevin Funk's avatar
Kevin Funk committed
495
    emit previewTextChanged(formatted);
496
497
498
499
}

CustomScriptPreferences::CustomScriptPreferences()
{
Kevin Funk's avatar
Kevin Funk committed
500
501
    m_updateTimer = new QTimer(this);
    m_updateTimer->setSingleShot(true);
502
    m_updateTimer->setInterval(1000);
Kevin Funk's avatar
Kevin Funk committed
503
504
    connect(m_updateTimer, &QTimer::timeout, this, &CustomScriptPreferences::updateTimeout);
    m_vLayout = new QVBoxLayout(this);
505
    m_vLayout->setMargin(0);
506
    m_captionLabel = new QLabel;
Kevin Funk's avatar
Kevin Funk committed
507
508
    m_vLayout->addWidget(m_captionLabel);
    m_vLayout->addSpacing(10);
509
    m_hLayout = new QHBoxLayout;
Kevin Funk's avatar
Kevin Funk committed
510
    m_vLayout->addLayout(m_hLayout);
511
    m_commandLabel = new QLabel;
Kevin Funk's avatar
Kevin Funk committed
512
    m_hLayout->addWidget(m_commandLabel);
513
    m_commandEdit = new QLineEdit;
Kevin Funk's avatar
Kevin Funk committed
514
515
516
    m_hLayout->addWidget(m_commandEdit);
    m_commandLabel->setText(i18n("Command:"));
    m_vLayout->addSpacing(10);
517
    m_bottomLabel = new QLabel;
Kevin Funk's avatar
Kevin Funk committed
518
519
520
521
522
523
524
525
526
527
    m_vLayout->addWidget(m_bottomLabel);
    m_bottomLabel->setTextFormat(Qt::RichText);
    m_bottomLabel->setText(
        i18n("<i>You can enter an arbitrary shell command.</i><br />"
             "The unformatted source-code is reached to the command <br />"
             "through the standard input, and the <br />"
             "formatted result is read from the standard output.<br />"
             "<br />"
             "If you add <b>$TMPFILE</b> into the command, then <br />"
             "a temporary file is used for transferring the code."));
528
    connect(m_commandEdit, &QLineEdit::textEdited,
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
529
            m_updateTimer, QOverload<>::of(&QTimer::start));
Kevin Funk's avatar
Kevin Funk committed
530
531
532
533
534
535

    m_vLayout->addSpacing(10);

    m_moreVariablesButton = new QPushButton(i18n("More Variables"));
    connect(m_moreVariablesButton, &QPushButton::clicked, this, &CustomScriptPreferences::moreVariablesClicked);
    m_vLayout->addWidget(m_moreVariablesButton);
536
    m_vLayout->addStretch();
537
538
}

Kevin Funk's avatar
Kevin Funk committed
539
void CustomScriptPreferences::load(const KDevelop::SourceFormatterStyle& style)
540
541
{
    m_style = style;
Kevin Funk's avatar
Kevin Funk committed
542
543
    m_commandEdit->setText(style.content());
    m_captionLabel->setText(i18n("Style: %1", style.caption()));
544
545
546
547
548
549
550
551
552

    updateTimeout();
}

QString CustomScriptPreferences::save()
{
    return m_commandEdit->text();
}

Kevin Funk's avatar
Kevin Funk committed
553
void CustomScriptPreferences::moreVariablesClicked(bool)
554
{
Kevin Funk's avatar
Kevin Funk committed
555
556
557
558
559
560
561
562
563
564
565
566
567
568
    KMessageBox::information(ICore::self()->uiController()->activeMainWindow(),
                             i18n("<b>$TMPFILE</b> will be replaced with the path to a temporary file. <br />"
                                  "The code will be written into the file, the temporary <br />"
                                  "file will be substituted into that position, and the result <br />"
                                  "will be read out of that file. <br />"
                                  "<br />"
                                  "<b>$FILE</b> will be replaced with the path of the original file. <br />"
                                  "The contents of the file must not be modified, changes are allowed <br />"
                                  "only in $TMPFILE.<br />"
                                  "<br />"
                                  "<b>${PROJECT_NAME}</b> will be replaced by the path of <br />"
                                  "the currently open project with the matching name."

                                  ), i18n("Variable Replacements"));
569
570
571
}

#include "customscript_plugin.moc"