1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "externaltool.h"
27 #include "externaltoolmanager.h"
28 
29 #include "icore.h"
30 #include "idocument.h"
31 #include "messagemanager.h"
32 #include "documentmanager.h"
33 #include "editormanager/editormanager.h"
34 #include "editormanager/ieditor.h"
35 
36 #include <app/app_version.h>
37 
38 #include <utils/algorithm.h>
39 #include <utils/fileutils.h>
40 #include <utils/macroexpander.h>
41 #include <utils/qtcassert.h>
42 #include <utils/qtcprocess.h>
43 
44 #include <QCoreApplication>
45 #include <QDateTime>
46 #include <QFileInfo>
47 #include <QXmlStreamReader>
48 #include <QXmlStreamWriter>
49 
50 using namespace Utils;
51 using namespace Core::Internal;
52 
53 namespace Core {
54 namespace Internal {
55 
56 const char kExternalTool[] = "externaltool";
57 const char kId[] = "id";
58 const char kDescription[] = "description";
59 const char kDisplayName[] = "displayname";
60 const char kCategory[] = "category";
61 const char kOrder[] = "order";
62 const char kExecutable[] = "executable";
63 const char kPath[] = "path";
64 const char kArguments[] = "arguments";
65 const char kInput[] = "input";
66 const char kWorkingDirectory[] = "workingdirectory";
67 const char kBaseEnvironmentId[] = "baseEnvironmentId";
68 const char kEnvironment[] = "environment";
69 
70 const char kXmlLang[] = "xml:lang";
71 const char kOutput[] = "output";
72 const char kError[] = "error";
73 const char kOutputShowInPane[] = "showinpane";
74 const char kOutputReplaceSelection[] = "replaceselection";
75 const char kOutputIgnore[] = "ignore";
76 const char kModifiesDocument[] = "modifiesdocument";
77 const char kYes[] = "yes";
78 const char kNo[] = "no";
79 const char kTrue[] = "true";
80 const char kFalse[] = "false";
81 
82 // #pragma mark -- ExternalTool
83 
ExternalTool()84 ExternalTool::ExternalTool() :
85     m_displayCategory("") // difference between isNull and isEmpty
86 {
87 }
88 
ExternalTool(const ExternalTool * other)89 ExternalTool::ExternalTool(const ExternalTool *other)
90     : m_id(other->m_id),
91       m_description(other->m_description),
92       m_displayName(other->m_displayName),
93       m_displayCategory(other->m_displayCategory),
94       m_order(other->m_order),
95       m_executables(other->m_executables),
96       m_arguments(other->m_arguments),
97       m_input(other->m_input),
98       m_workingDirectory(other->m_workingDirectory),
99       m_baseEnvironmentProviderId(other->m_baseEnvironmentProviderId),
100       m_environment(other->m_environment),
101       m_outputHandling(other->m_outputHandling),
102       m_errorHandling(other->m_errorHandling),
103       m_modifiesCurrentDocument(other->m_modifiesCurrentDocument),
104       m_fileName(other->m_fileName),
105       m_presetTool(other->m_presetTool)
106 {
107 }
108 
operator =(const ExternalTool & other)109 ExternalTool &ExternalTool::operator=(const ExternalTool &other)
110 {
111     m_id = other.m_id;
112     m_description = other.m_description;
113     m_displayName = other.m_displayName;
114     m_displayCategory = other.m_displayCategory;
115     m_order = other.m_order;
116     m_executables = other.m_executables;
117     m_arguments = other.m_arguments;
118     m_input = other.m_input;
119     m_workingDirectory = other.m_workingDirectory;
120     m_environment = other.m_environment;
121     m_outputHandling = other.m_outputHandling;
122     m_errorHandling = other.m_errorHandling;
123     m_modifiesCurrentDocument = other.m_modifiesCurrentDocument;
124     m_fileName = other.m_fileName;
125     m_presetFileName = other.m_presetFileName;
126     m_presetTool = other.m_presetTool;
127     return *this;
128 }
129 
130 ExternalTool::~ExternalTool() = default;
131 
id() const132 QString ExternalTool::id() const
133 {
134     return m_id;
135 }
136 
description() const137 QString ExternalTool::description() const
138 {
139     return m_description;
140 }
141 
displayName() const142 QString ExternalTool::displayName() const
143 {
144     return m_displayName;
145 }
146 
displayCategory() const147 QString ExternalTool::displayCategory() const
148 {
149     return m_displayCategory;
150 }
151 
order() const152 int ExternalTool::order() const
153 {
154     return m_order;
155 }
156 
executables() const157 QStringList ExternalTool::executables() const
158 {
159     return m_executables;
160 }
161 
arguments() const162 QString ExternalTool::arguments() const
163 {
164     return m_arguments;
165 }
166 
input() const167 QString ExternalTool::input() const
168 {
169     return m_input;
170 }
171 
workingDirectory() const172 QString ExternalTool::workingDirectory() const
173 {
174     return m_workingDirectory;
175 }
176 
baseEnvironmentProviderId() const177 Id ExternalTool::baseEnvironmentProviderId() const
178 {
179     return m_baseEnvironmentProviderId;
180 }
181 
baseEnvironment() const182 Environment ExternalTool::baseEnvironment() const
183 {
184     if (m_baseEnvironmentProviderId.isValid()) {
185         const optional<EnvironmentProvider> provider = EnvironmentProvider::provider(
186             m_baseEnvironmentProviderId.name());
187         if (provider && provider->environment)
188             return provider->environment();
189     }
190     return Environment::systemEnvironment();
191 }
192 
environmentUserChanges() const193 EnvironmentItems ExternalTool::environmentUserChanges() const
194 {
195     return m_environment;
196 }
197 
outputHandling() const198 ExternalTool::OutputHandling ExternalTool::outputHandling() const
199 {
200     return m_outputHandling;
201 }
202 
errorHandling() const203 ExternalTool::OutputHandling ExternalTool::errorHandling() const
204 {
205     return m_errorHandling;
206 }
207 
modifiesCurrentDocument() const208 bool ExternalTool::modifiesCurrentDocument() const
209 {
210     return m_modifiesCurrentDocument;
211 }
212 
setFileName(const QString & fileName)213 void ExternalTool::setFileName(const QString &fileName)
214 {
215     m_fileName = fileName;
216 }
217 
setPreset(QSharedPointer<ExternalTool> preset)218 void ExternalTool::setPreset(QSharedPointer<ExternalTool> preset)
219 {
220     m_presetTool = preset;
221 }
222 
fileName() const223 QString ExternalTool::fileName() const
224 {
225     return m_fileName;
226 }
227 
preset() const228 QSharedPointer<ExternalTool> ExternalTool::preset() const
229 {
230     return m_presetTool;
231 }
232 
setId(const QString & id)233 void ExternalTool::setId(const QString &id)
234 {
235     m_id = id;
236 }
237 
setDisplayCategory(const QString & category)238 void ExternalTool::setDisplayCategory(const QString &category)
239 {
240     m_displayCategory = category;
241 }
242 
setDisplayName(const QString & name)243 void ExternalTool::setDisplayName(const QString &name)
244 {
245     m_displayName = name;
246 }
247 
setDescription(const QString & description)248 void ExternalTool::setDescription(const QString &description)
249 {
250     m_description = description;
251 }
252 
253 
setOutputHandling(OutputHandling handling)254 void ExternalTool::setOutputHandling(OutputHandling handling)
255 {
256     m_outputHandling = handling;
257 }
258 
259 
setErrorHandling(OutputHandling handling)260 void ExternalTool::setErrorHandling(OutputHandling handling)
261 {
262     m_errorHandling = handling;
263 }
264 
265 
setModifiesCurrentDocument(bool modifies)266 void ExternalTool::setModifiesCurrentDocument(bool modifies)
267 {
268     m_modifiesCurrentDocument = modifies;
269 }
270 
271 
setExecutables(const QStringList & executables)272 void ExternalTool::setExecutables(const QStringList &executables)
273 {
274     m_executables = executables;
275 }
276 
277 
setArguments(const QString & arguments)278 void ExternalTool::setArguments(const QString &arguments)
279 {
280     m_arguments = arguments;
281 }
282 
283 
setInput(const QString & input)284 void ExternalTool::setInput(const QString &input)
285 {
286     m_input = input;
287 }
288 
289 
setWorkingDirectory(const QString & workingDirectory)290 void ExternalTool::setWorkingDirectory(const QString &workingDirectory)
291 {
292     m_workingDirectory = workingDirectory;
293 }
294 
setBaseEnvironmentProviderId(Id id)295 void ExternalTool::setBaseEnvironmentProviderId(Id id)
296 {
297     m_baseEnvironmentProviderId = id;
298 }
299 
setEnvironmentUserChanges(const EnvironmentItems & items)300 void ExternalTool::setEnvironmentUserChanges(const EnvironmentItems &items)
301 {
302     m_environment = items;
303 }
304 
splitLocale(const QString & locale)305 static QStringList splitLocale(const QString &locale)
306 {
307     QString value = locale;
308     QStringList values;
309     if (!value.isEmpty())
310         values << value;
311     int index = value.indexOf(QLatin1Char('.'));
312     if (index >= 0) {
313         value = value.left(index);
314         if (!value.isEmpty())
315             values << value;
316     }
317     index = value.indexOf(QLatin1Char('_'));
318     if (index >= 0) {
319         value = value.left(index);
320         if (!value.isEmpty())
321             values << value;
322     }
323     return values;
324 }
325 
localizedText(const QStringList & locales,QXmlStreamReader * reader,int * currentLocale,QString * currentText)326 static void localizedText(const QStringList &locales, QXmlStreamReader *reader, int *currentLocale, QString *currentText)
327 {
328     Q_ASSERT(reader);
329     Q_ASSERT(currentLocale);
330     Q_ASSERT(currentText);
331     if (reader->attributes().hasAttribute(kXmlLang)) {
332         int index = locales.indexOf(reader->attributes().value(kXmlLang).toString());
333         if (index >= 0 && (index < *currentLocale || *currentLocale < 0)) {
334             *currentText = reader->readElementText();
335             *currentLocale = index;
336         } else {
337             reader->skipCurrentElement();
338         }
339     } else {
340         if (*currentLocale < 0 && currentText->isEmpty()) {
341             *currentText = QCoreApplication::translate("Core::Internal::ExternalTool",
342                                                        reader->readElementText().toUtf8().constData(),
343                                                        "");
344         } else {
345             reader->skipCurrentElement();
346         }
347     }
348     if (currentText->isNull()) // prefer isEmpty over isNull
349         *currentText = "";
350 }
351 
parseOutputAttribute(const QString & attribute,QXmlStreamReader * reader,ExternalTool::OutputHandling * value)352 static bool parseOutputAttribute(const QString &attribute, QXmlStreamReader *reader, ExternalTool::OutputHandling *value)
353 {
354     const auto output = reader->attributes().value(attribute);
355     if (output == QLatin1String(kOutputShowInPane)) {
356         *value = ExternalTool::ShowInPane;
357     } else if (output == QLatin1String(kOutputReplaceSelection)) {
358         *value = ExternalTool::ReplaceSelection;
359     } else if (output == QLatin1String(kOutputIgnore)) {
360         *value = ExternalTool::Ignore;
361     } else {
362         reader->raiseError("Allowed values for output attribute are 'showinpane','replaceselection','ignore'");
363         return false;
364     }
365     return true;
366 }
367 
createFromXml(const QByteArray & xml,QString * errorMessage,const QString & locale)368 ExternalTool * ExternalTool::createFromXml(const QByteArray &xml, QString *errorMessage, const QString &locale)
369 {
370     int descriptionLocale = -1;
371     int nameLocale = -1;
372     int categoryLocale = -1;
373     const QStringList &locales = splitLocale(locale);
374     auto tool = new ExternalTool;
375     QXmlStreamReader reader(xml);
376 
377     if (!reader.readNextStartElement() || reader.name() != QLatin1String(kExternalTool))
378         reader.raiseError("Missing start element <externaltool>");
379     tool->m_id = reader.attributes().value(kId).toString();
380     if (tool->m_id.isEmpty())
381         reader.raiseError("Missing or empty id attribute for <externaltool>");
382     while (reader.readNextStartElement()) {
383         if (reader.name() == QLatin1String(kDescription)) {
384             localizedText(locales, &reader, &descriptionLocale, &tool->m_description);
385         } else if (reader.name() == QLatin1String(kDisplayName)) {
386             localizedText(locales, &reader, &nameLocale, &tool->m_displayName);
387         } else if (reader.name() == QLatin1String(kCategory)) {
388             localizedText(locales, &reader, &categoryLocale, &tool->m_displayCategory);
389         } else if (reader.name() == QLatin1String(kOrder)) {
390             if (tool->m_order >= 0) {
391                 reader.raiseError("only one <order> element allowed");
392                 break;
393             }
394             bool ok;
395             tool->m_order = reader.readElementText().toInt(&ok);
396             if (!ok || tool->m_order < 0)
397                 reader.raiseError("<order> element requires non-negative integer value");
398         } else if (reader.name() == QLatin1String(kExecutable)) {
399             if (reader.attributes().hasAttribute(kOutput)) {
400                 if (!parseOutputAttribute(kOutput, &reader, &tool->m_outputHandling))
401                     break;
402             }
403             if (reader.attributes().hasAttribute(kError)) {
404                 if (!parseOutputAttribute(kError, &reader, &tool->m_errorHandling))
405                     break;
406             }
407             if (reader.attributes().hasAttribute(kModifiesDocument)) {
408                 const auto value = reader.attributes().value(kModifiesDocument);
409                 if (value == QLatin1String(kYes) || value == QLatin1String(kTrue)) {
410                     tool->m_modifiesCurrentDocument = true;
411                 } else if (value == QLatin1String(kNo) || value == QLatin1String(kFalse)) {
412                     tool->m_modifiesCurrentDocument = false;
413                 } else {
414                     reader.raiseError("Allowed values for modifiesdocument attribute are 'yes','true','no','false'");
415                     break;
416                 }
417             }
418             while (reader.readNextStartElement()) {
419                 if (reader.name() == QLatin1String(kPath)) {
420                     tool->m_executables.append(reader.readElementText());
421                 } else if (reader.name() == QLatin1String(kArguments)) {
422                     if (!tool->m_arguments.isEmpty()) {
423                         reader.raiseError("only one <arguments> element allowed");
424                         break;
425                     }
426                     tool->m_arguments = reader.readElementText();
427                 } else if (reader.name() == QLatin1String(kInput)) {
428                     if (!tool->m_input.isEmpty()) {
429                         reader.raiseError("only one <input> element allowed");
430                         break;
431                     }
432                     tool->m_input = reader.readElementText();
433                 } else if (reader.name() == QLatin1String(kWorkingDirectory)) {
434                     if (!tool->m_workingDirectory.isEmpty()) {
435                         reader.raiseError("only one <workingdirectory> element allowed");
436                         break;
437                     }
438                     tool->m_workingDirectory = reader.readElementText();
439                 } else if (reader.name() == QLatin1String(kBaseEnvironmentId)) {
440                     if (tool->m_baseEnvironmentProviderId.isValid()) {
441                         reader.raiseError("only one <baseEnvironmentId> element allowed");
442                         break;
443                     }
444                     tool->m_baseEnvironmentProviderId = Id::fromString(reader.readElementText());
445                 } else if (reader.name() == QLatin1String(kEnvironment)) {
446                     if (!tool->m_environment.isEmpty()) {
447                         reader.raiseError("only one <environment> element allowed");
448                         break;
449                     }
450                     QStringList lines = reader.readElementText().split(QLatin1Char(';'));
451                     for (auto iter = lines.begin(); iter != lines.end(); ++iter)
452                         *iter = QString::fromUtf8(QByteArray::fromPercentEncoding(iter->toUtf8()));
453                     tool->m_environment = EnvironmentItem::fromStringList(lines);
454                 } else {
455                     reader.raiseError(QString::fromLatin1("Unknown element <%1> as subelement of <%2>").arg(
456                                           reader.qualifiedName().toString(), QString(kExecutable)));
457                     break;
458                 }
459             }
460         } else {
461             reader.raiseError(QString::fromLatin1("Unknown element <%1>").arg(reader.qualifiedName().toString()));
462         }
463     }
464     if (reader.hasError()) {
465         if (errorMessage)
466             *errorMessage = reader.errorString();
467         delete tool;
468         return nullptr;
469     }
470     return tool;
471 }
472 
createFromFile(const QString & fileName,QString * errorMessage,const QString & locale)473 ExternalTool * ExternalTool::createFromFile(const QString &fileName, QString *errorMessage, const QString &locale)
474 {
475     QString absFileName = QFileInfo(fileName).absoluteFilePath();
476     FileReader reader;
477     if (!reader.fetch(FilePath::fromString(absFileName), errorMessage))
478         return nullptr;
479     ExternalTool *tool = ExternalTool::createFromXml(reader.data(), errorMessage, locale);
480     if (!tool)
481         return nullptr;
482     tool->m_fileName = absFileName;
483     return tool;
484 }
485 
stringForOutputHandling(ExternalTool::OutputHandling handling)486 static QString stringForOutputHandling(ExternalTool::OutputHandling handling)
487 {
488     switch (handling) {
489     case ExternalTool::Ignore:
490         return QLatin1String(kOutputIgnore);
491     case ExternalTool::ShowInPane:
492         return QLatin1String(kOutputShowInPane);
493     case ExternalTool::ReplaceSelection:
494         return QLatin1String(kOutputReplaceSelection);
495     }
496     return QString();
497 }
498 
save(QString * errorMessage) const499 bool ExternalTool::save(QString *errorMessage) const
500 {
501     if (m_fileName.isEmpty())
502         return false;
503     FileSaver saver(FilePath::fromString(m_fileName));
504     if (!saver.hasError()) {
505         QXmlStreamWriter out(saver.file());
506         out.setAutoFormatting(true);
507         out.writeStartDocument("1.0");
508         out.writeComment(QString::fromLatin1("Written on %1 by %2")
509                          .arg(QDateTime::currentDateTime().toString(), ICore::versionString()));
510         out.writeStartElement(kExternalTool);
511         out.writeAttribute(kId, m_id);
512         out.writeTextElement(kDescription, m_description);
513         out.writeTextElement(kDisplayName, m_displayName);
514         out.writeTextElement(kCategory, m_displayCategory);
515         if (m_order != -1)
516             out.writeTextElement(kOrder, QString::number(m_order));
517 
518         out.writeStartElement(kExecutable);
519         out.writeAttribute(kOutput, stringForOutputHandling(m_outputHandling));
520         out.writeAttribute(kError, stringForOutputHandling(m_errorHandling));
521         out.writeAttribute(kModifiesDocument, QLatin1String(m_modifiesCurrentDocument ? kYes : kNo));
522         foreach (const QString &executable, m_executables)
523             out.writeTextElement(kPath, executable);
524         if (!m_arguments.isEmpty())
525             out.writeTextElement(kArguments, m_arguments);
526         if (!m_input.isEmpty())
527             out.writeTextElement(kInput, m_input);
528         if (!m_workingDirectory.isEmpty())
529             out.writeTextElement(kWorkingDirectory, m_workingDirectory);
530         if (m_baseEnvironmentProviderId.isValid())
531             out.writeTextElement(kBaseEnvironmentId, m_baseEnvironmentProviderId.toString());
532         if (!m_environment.isEmpty()) {
533             QStringList envLines = EnvironmentItem::toStringList(m_environment);
534             for (auto iter = envLines.begin(); iter != envLines.end(); ++iter)
535                 *iter = QString::fromUtf8(iter->toUtf8().toPercentEncoding());
536             out.writeTextElement(kEnvironment, envLines.join(QLatin1Char(';')));
537         }
538         out.writeEndElement();
539 
540         out.writeEndDocument();
541 
542         saver.setResult(&out);
543     }
544     return saver.finalize(errorMessage);
545 }
546 
operator ==(const ExternalTool & other) const547 bool ExternalTool::operator==(const ExternalTool &other) const
548 {
549     return m_id == other.m_id
550             && m_description == other.m_description
551             && m_displayName == other.m_displayName
552             && m_displayCategory == other.m_displayCategory
553             && m_order == other.m_order
554             && m_executables == other.m_executables
555             && m_arguments == other.m_arguments
556             && m_input == other.m_input
557             && m_workingDirectory == other.m_workingDirectory
558             && m_baseEnvironmentProviderId == other.m_baseEnvironmentProviderId
559             && m_environment == other.m_environment
560             && m_outputHandling == other.m_outputHandling
561             && m_modifiesCurrentDocument == other.m_modifiesCurrentDocument
562             && m_errorHandling == other.m_errorHandling
563             && m_fileName == other.m_fileName;
564 }
565 
566 // #pragma mark -- ExternalToolRunner
567 
ExternalToolRunner(const ExternalTool * tool)568 ExternalToolRunner::ExternalToolRunner(const ExternalTool *tool)
569     : m_tool(new ExternalTool(tool)),
570       m_process(nullptr),
571       m_outputCodec(QTextCodec::codecForLocale()),
572       m_hasError(false)
573 {
574     run();
575 }
576 
~ExternalToolRunner()577 ExternalToolRunner::~ExternalToolRunner()
578 {
579     if (m_tool)
580         delete m_tool;
581 }
582 
hasError() const583 bool ExternalToolRunner::hasError() const
584 {
585     return m_hasError;
586 }
587 
errorString() const588 QString ExternalToolRunner::errorString() const
589 {
590     return m_errorString;
591 }
592 
resolve()593 bool ExternalToolRunner::resolve()
594 {
595     if (!m_tool)
596         return false;
597     m_resolvedExecutable.clear();
598     m_resolvedArguments.clear();
599     m_resolvedWorkingDirectory.clear();
600     m_resolvedEnvironment = m_tool->baseEnvironment();
601 
602     MacroExpander *expander = globalMacroExpander();
603     EnvironmentItems expandedEnvironment = Utils::transform(
604         m_tool->environmentUserChanges(), [expander](const EnvironmentItem &item) {
605             return EnvironmentItem(item.name, expander->expand(item.value), item.operation);
606         });
607     m_resolvedEnvironment.modify(expandedEnvironment);
608 
609     {
610         // executable
611         QStringList expandedExecutables; /* for error message */
612         foreach (const QString &executable, m_tool->executables()) {
613             QString expanded = expander->expand(executable);
614             expandedExecutables.append(expanded);
615             m_resolvedExecutable = m_resolvedEnvironment.searchInPath(expanded);
616             if (!m_resolvedExecutable.isEmpty())
617                 break;
618         }
619         if (m_resolvedExecutable.isEmpty()) {
620             m_hasError = true;
621             for (int i = 0; i < expandedExecutables.size(); ++i) {
622                 m_errorString += tr("Could not find executable for \"%1\" (expanded \"%2\")")
623                         .arg(m_tool->executables().at(i), expandedExecutables.at(i));
624                 m_errorString += QLatin1Char('\n');
625             }
626             if (!m_errorString.isEmpty())
627                 m_errorString.chop(1);
628             return false;
629         }
630     }
631 
632     m_resolvedArguments = expander->expandProcessArgs(m_tool->arguments());
633     m_resolvedInput = expander->expand(m_tool->input());
634     m_resolvedWorkingDirectory = expander->expand(m_tool->workingDirectory());
635 
636     return true;
637 }
638 
run()639 void ExternalToolRunner::run()
640 {
641     if (!resolve()) {
642         deleteLater();
643         return;
644     }
645     if (m_tool->modifiesCurrentDocument()) {
646         if (IDocument *document = EditorManager::currentDocument()) {
647             m_expectedFilePath = document->filePath();
648             if (!DocumentManager::saveModifiedDocument(document)) {
649                 deleteLater();
650                 return;
651             }
652             DocumentManager::expectFileChange(m_expectedFilePath);
653         }
654     }
655     m_process = new QtcProcess(this);
656     connect(m_process, &QtcProcess::started, this, &ExternalToolRunner::started);
657     connect(m_process, &QtcProcess::finished, this, &ExternalToolRunner::finished);
658     connect(m_process, &QtcProcess::errorOccurred, this, &ExternalToolRunner::error);
659     connect(m_process, &QtcProcess::readyReadStandardOutput,
660             this, &ExternalToolRunner::readStandardOutput);
661     connect(m_process, &QtcProcess::readyReadStandardError,
662             this, &ExternalToolRunner::readStandardError);
663     if (!m_resolvedWorkingDirectory.isEmpty())
664         m_process->setWorkingDirectory(m_resolvedWorkingDirectory);
665     const CommandLine cmd{m_resolvedExecutable, m_resolvedArguments, CommandLine::Raw};
666     m_process->setCommand(cmd);
667     m_process->setEnvironment(m_resolvedEnvironment);
668     const auto write = m_tool->outputHandling() == ExternalTool::ShowInPane
669                            ? QOverload<const QString &>::of(MessageManager::writeDisrupting)
670                            : QOverload<const QString &>::of(MessageManager::writeSilently);
671     write(tr("Starting external tool \"%1\"").arg(cmd.toUserOutput()));
672     m_process->start();
673 }
674 
started()675 void ExternalToolRunner::started()
676 {
677     if (!m_resolvedInput.isEmpty())
678         m_process->write(m_resolvedInput.toLocal8Bit());
679     m_process->closeWriteChannel();
680 }
681 
finished()682 void ExternalToolRunner::finished()
683 {
684     if (m_process->result() == QtcProcess::FinishedWithSuccess
685             &&  (m_tool->outputHandling() == ExternalTool::ReplaceSelection
686                  || m_tool->errorHandling() == ExternalTool::ReplaceSelection)) {
687         ExternalToolManager::emitReplaceSelectionRequested(m_processOutput);
688     }
689     if (m_tool->modifiesCurrentDocument())
690         DocumentManager::unexpectFileChange(m_expectedFilePath);
691     const auto write = m_tool->outputHandling() == ExternalTool::ShowInPane
692                            ? QOverload<const QString &>::of(MessageManager::writeFlashing)
693                            : QOverload<const QString &>::of(MessageManager::writeSilently);
694     write(tr("\"%1\" finished").arg(m_resolvedExecutable.toUserOutput()));
695     deleteLater();
696 }
697 
error(QProcess::ProcessError error)698 void ExternalToolRunner::error(QProcess::ProcessError error)
699 {
700     if (m_tool->modifiesCurrentDocument())
701         DocumentManager::unexpectFileChange(m_expectedFilePath);
702     // TODO inform about errors
703     Q_UNUSED(error)
704     deleteLater();
705 }
706 
readStandardOutput()707 void ExternalToolRunner::readStandardOutput()
708 {
709     if (m_tool->outputHandling() == ExternalTool::Ignore)
710         return;
711     const QByteArray data = m_process->readAllStandardOutput();
712     const QString output = m_outputCodec->toUnicode(data.constData(),
713                                                     data.length(),
714                                                     &m_outputCodecState);
715     if (m_tool->outputHandling() == ExternalTool::ShowInPane)
716         MessageManager::writeSilently(output);
717     else if (m_tool->outputHandling() == ExternalTool::ReplaceSelection)
718         m_processOutput.append(output);
719 }
720 
readStandardError()721 void ExternalToolRunner::readStandardError()
722 {
723     if (m_tool->errorHandling() == ExternalTool::Ignore)
724         return;
725     const QByteArray data = m_process->readAllStandardError();
726     const QString output = m_outputCodec->toUnicode(data.constData(),
727                                                     data.length(),
728                                                     &m_errorCodecState);
729     if (m_tool->errorHandling() == ExternalTool::ShowInPane)
730         MessageManager::writeSilently(output);
731     else if (m_tool->errorHandling() == ExternalTool::ReplaceSelection)
732         m_processOutput.append(output);
733 }
734 
735 } // namespace Internal
736 
737 } // namespace Core
738