From 04a99c1de16e1d04bcc1908fec7464d15b421702 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 19 Mar 2020 16:00:37 +0100 Subject: [PATCH] Remove the limitation that output formatters have to be exclusive Introduce an aggregating output formatter that forwards its input to a sub-formatter that feels responsible for it, or otherwise lets the base class handle it. Our output panes now use such an aggregating formatter. In particular, this means that in the future, we won't have to stuff all run control output formatting into the Qt output formatter anymore. Change-Id: I5498f200a61db10ccff3ec8974c6825da7f7072d Reviewed-by: hjk --- src/libs/utils/outputformatter.cpp | 86 ++++++++++++- src/libs/utils/outputformatter.h | 39 ++++-- src/plugins/coreplugin/coreplugin.h | 2 + src/plugins/coreplugin/outputwindow.cpp | 114 ++++++++++++++++-- src/plugins/coreplugin/outputwindow.h | 3 +- src/plugins/projectexplorer/appoutputpane.cpp | 12 +- .../projectexplorer/compileoutputwindow.cpp | 2 +- src/plugins/projectexplorer/runcontrol.cpp | 28 ++--- src/plugins/projectexplorer/runcontrol.h | 4 +- src/plugins/python/pythonrunconfiguration.cpp | 20 ++- src/plugins/qtsupport/qtoutputformatter.cpp | 67 ++++++---- .../serialterminal/serialoutputpane.cpp | 2 +- src/plugins/vcsbase/vcsoutputformatter.cpp | 13 +- src/plugins/vcsbase/vcsoutputformatter.h | 5 +- src/plugins/vcsbase/vcsoutputwindow.cpp | 2 +- 15 files changed, 314 insertions(+), 85 deletions(-) diff --git a/src/libs/utils/outputformatter.cpp b/src/libs/utils/outputformatter.cpp index ea40ddadeee..a1a98a1fbec 100644 --- a/src/libs/utils/outputformatter.cpp +++ b/src/libs/utils/outputformatter.cpp @@ -25,8 +25,10 @@ #include "ansiescapecodehandler.h" #include "outputformatter.h" +#include "qtcassert.h" #include "synchronousprocess.h" #include "theme/theme.h" +#include "utils/optional.h" #include #include @@ -44,6 +46,7 @@ public: QTextCursor cursor; AnsiEscapeCodeHandler escapeCodeHandler; QPair incompleteLine; + optional formatOverride; bool boldFontEnabled = true; bool prependCarriageReturn = false; }; @@ -75,9 +78,15 @@ void OutputFormatter::setPlainTextEdit(QPlainTextEdit *plainText) void OutputFormatter::doAppendMessage(const QString &text, OutputFormat format) { - if (!d->cursor.atEnd() && text.startsWith('\n')) - d->cursor.movePosition(QTextCursor::End); - doAppendMessage(text, d->formats[format]); + if (handleMessage(text, format) == Status::NotHandled) + appendMessageDefault(text, format); +} + +OutputFormatter::Status OutputFormatter::handleMessage(const QString &text, OutputFormat format) +{ + Q_UNUSED(text); + Q_UNUSED(format); + return Status::NotHandled; } void OutputFormatter::doAppendMessage(const QString &text, const QTextCharFormat &format) @@ -118,6 +127,16 @@ QTextCharFormat OutputFormatter::linkFormat(const QTextCharFormat &inputFormat, return result; } +void OutputFormatter::overrideTextCharFormat(const QTextCharFormat &fmt) +{ + d->formatOverride = fmt; +} + +void OutputFormatter::appendMessageDefault(const QString &text, OutputFormat format) +{ + doAppendMessage(text, d->formatOverride ? d->formatOverride.value() : d->formats[format]); +} + void OutputFormatter::clearLastLine() { // Note that this approach will fail if the text edit is not read-only and users @@ -158,9 +177,10 @@ void OutputFormatter::dumpIncompleteLine(const QString &line, OutputFormat forma d->incompleteLine.second = format; } -void OutputFormatter::handleLink(const QString &href) +bool OutputFormatter::handleLink(const QString &href) { Q_UNUSED(href) + return false; } void OutputFormatter::clear() @@ -236,4 +256,62 @@ void OutputFormatter::appendMessage(const QString &text, OutputFormat format) } } +class AggregatingOutputFormatter::Private +{ +public: + QList formatters; + OutputFormatter *nextFormatter = nullptr; +}; + +AggregatingOutputFormatter::AggregatingOutputFormatter() : d(new Private) {} +AggregatingOutputFormatter::~AggregatingOutputFormatter() { delete d; } + +void AggregatingOutputFormatter::setFormatters(const QList &formatters) +{ + for (OutputFormatter * const f : formatters) + f->setPlainTextEdit(plainTextEdit()); + d->formatters = formatters; + d->nextFormatter = nullptr; +} + +OutputFormatter::Status AggregatingOutputFormatter::handleMessage(const QString &text, + OutputFormat format) +{ + if (d->nextFormatter) { + switch (d->nextFormatter->handleMessage(text, format)) { + case Status::Done: + d->nextFormatter = nullptr; + return Status::Done; + case Status::InProgress: + return Status::InProgress; + case Status::NotHandled: + QTC_CHECK(false); + d->nextFormatter = nullptr; + return Status::NotHandled; + } + } + QTC_CHECK(!d->nextFormatter); + for (OutputFormatter * const formatter : qAsConst(d->formatters)) { + switch (formatter->handleMessage(text, format)) { + case Status::Done: + return Status::Done; + case Status::InProgress: + d->nextFormatter = formatter; + return Status::InProgress; + case Status::NotHandled: + break; + } + } + return Status::NotHandled; +} + +bool AggregatingOutputFormatter::handleLink(const QString &href) +{ + for (OutputFormatter * const f : qAsConst(d->formatters)) { + if (f->handleLink(href)) + return true; + } + return false; +} + } // namespace Utils diff --git a/src/libs/utils/outputformatter.h b/src/libs/utils/outputformatter.h index a5d9c4b331c..0aaf8807143 100644 --- a/src/libs/utils/outputformatter.h +++ b/src/libs/utils/outputformatter.h @@ -42,8 +42,11 @@ class FormattedText; namespace Internal { class OutputFormatterPrivate; } +class QTCREATOR_UTILS_EXPORT AggregatingOutputFormatter; + class QTCREATOR_UTILS_EXPORT OutputFormatter : public QObject { + friend class AggregatingOutputFormatter; public: OutputFormatter(); ~OutputFormatter() override; @@ -55,26 +58,32 @@ public: void appendMessage(const QString &text, OutputFormat format); - virtual void handleLink(const QString &href); + virtual bool handleLink(const QString &href); void clear(); void setBoldFontEnabled(bool enabled); static QTextCharFormat linkFormat(const QTextCharFormat &inputFormat, const QString &href); + // For unit testing only + void overrideTextCharFormat(const QTextCharFormat &fmt); + protected: - // text contains at most one line feed character, and if it does occur, it's the last character. - // Either way, the input is to be considered "complete" for formatting purposes. - virtual void doAppendMessage(const QString &text, OutputFormat format); - - virtual void clearLastLine(); + enum class Status { Done, InProgress, NotHandled }; + void appendMessageDefault(const QString &text, OutputFormat format); + void clearLastLine(); QTextCharFormat charFormat(OutputFormat format) const; QList parseAnsi(const QString &text, const QTextCharFormat &format); QTextCursor &cursor() const; private: - void doAppendMessage(const QString &text, const QTextCharFormat &format); + // text contains at most one line feed character, and if it does occur, it's the last character. + // Either way, the input is to be considered "complete" for formatting purposes. + void doAppendMessage(const QString &text, OutputFormat format); + + virtual Status handleMessage(const QString &text, OutputFormat format); virtual void reset() {} + void doAppendMessage(const QString &text, const QTextCharFormat &format); void append(const QString &text, const QTextCharFormat &format); void initFormats(); void flushIncompleteLine(); @@ -83,4 +92,20 @@ private: Internal::OutputFormatterPrivate *d; }; +class QTCREATOR_UTILS_EXPORT AggregatingOutputFormatter : public OutputFormatter +{ +public: + AggregatingOutputFormatter(); + ~AggregatingOutputFormatter(); + + void setFormatters(const QList &formatters); + bool handleLink(const QString &href) override; + +private: + Status handleMessage(const QString &text, OutputFormat format) override; + + class Private; + Private * const d; +}; + } // namespace Utils diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index fee1458eabd..04c0b1d1178 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -76,6 +76,8 @@ private slots: // Locator: void test_basefilefilter(); void test_basefilefilter_data(); + + void testOutputFormatter(); #endif private: diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp index fc61bcc7501..e6b03b2c7aa 100644 --- a/src/plugins/coreplugin/outputwindow.cpp +++ b/src/plugins/coreplugin/outputwindow.cpp @@ -27,6 +27,7 @@ #include "actionmanager/actionmanager.h" #include "coreconstants.h" +#include "coreplugin.h" #include "icore.h" #include @@ -39,6 +40,10 @@ #include #include +#ifdef WITH_TESTS +#include +#endif + using namespace Utils; namespace Core { @@ -60,9 +65,8 @@ public: } IContext *outputWindowContext = nullptr; - QPointer formatter; QString settingsKey; - OutputFormatter defaultFormatter; + AggregatingOutputFormatter formatter; bool scrollToBottom = true; bool linksActive = true; @@ -91,7 +95,7 @@ OutputWindow::OutputWindow(Context context, const QString &settingsKey, QWidget setFrameShape(QFrame::NoFrame); setMouseTracking(true); setUndoRedoEnabled(false); - setFormatter(&d->defaultFormatter); + d->formatter.setPlainTextEdit(this); d->settingsKey = settingsKey; @@ -168,7 +172,7 @@ void OutputWindow::mouseReleaseEvent(QMouseEvent *e) { if (d->linksActive && d->mouseButtonPressed == Qt::LeftButton) { const QString href = anchorAt(e->pos()); - d->formatter->handleLink(href); + d->formatter.handleLink(href); } // Mouse was released, activate links again @@ -212,13 +216,9 @@ void OutputWindow::keyPressEvent(QKeyEvent *ev) verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum); } -void OutputWindow::setFormatter(OutputFormatter *formatter) +void OutputWindow::setFormatters(const QList &formatters) { - d->formatter = formatter; - if (d->formatter) - d->formatter->setPlainTextEdit(this); - else - d->formatter = &d->defaultFormatter; + d->formatter.setFormatters(formatters); } void OutputWindow::showEvent(QShowEvent *e) @@ -396,7 +396,7 @@ void OutputWindow::appendMessage(const QString &output, OutputFormat format) const bool atBottom = isScrollbarAtBottom() || m_scrollTimer.isActive(); d->scrollToBottom = true; - d->formatter->appendMessage(out, format); + d->formatter.appendMessage(out, format); if (atBottom) { if (m_lastMessage.elapsed() < 5) { @@ -445,7 +445,12 @@ QMimeData *OutputWindow::createMimeDataFromSelection() const void OutputWindow::clear() { - d->formatter->clear(); + d->formatter.clear(); +} + +void OutputWindow::flush() +{ + d->formatter.flush(); } void OutputWindow::scrollToBottom() @@ -494,4 +499,89 @@ void OutputWindow::setWordWrapEnabled(bool wrap) setWordWrapMode(QTextOption::NoWrap); } +#ifdef WITH_TESTS + +// Handles all lines starting with "A" and the following ones up to and including the next +// one starting with "A". +class TestFormatterA : public OutputFormatter +{ +private: + Status handleMessage(const QString &text, OutputFormat format) override + { + if (m_handling) { + appendMessageDefault("handled by A\n", format); + if (text.startsWith("A")) { + m_handling = false; + return Status::Done; + } + return Status::InProgress; + } + if (text.startsWith("A")) { + m_handling = true; + appendMessageDefault("handled by A\n", format); + return Status::InProgress; + } + return Status::NotHandled; + } + + void reset() override { m_handling = false; } + + bool m_handling = false; +}; + +// Handles all lines starting with "B". No continuation logic +class TestFormatterB : public OutputFormatter +{ +private: + Status handleMessage(const QString &text, OutputFormat format) override + { + if (text.startsWith("B")) { + appendMessageDefault("handled by B\n", format); + return Status::Done; + } + return Status::NotHandled; + } +}; + +void Internal::CorePlugin::testOutputFormatter() +{ + const QString input = + "B to be handled by B\r\n" + "not to be handled\n" + "A to be handled by A\n" + "continuation for A\r\n" + "B looks like B, but still continuation for A\r\n" + "A end of A\n" + "A next A\n" + "A end of next A\n" + " A trick\r\n" + "B to be handled by B\n"; + const QString output = + "handled by B\n" + "not to be handled\n" + "handled by A\n" + "handled by A\n" + "handled by A\n" + "handled by A\n" + "handled by A\n" + "handled by A\n" + " A trick\n" + "handled by B\n"; + TestFormatterA formatterA; + TestFormatterB formatterB; + AggregatingOutputFormatter formatter; + QPlainTextEdit textEdit; + formatter.setPlainTextEdit(&textEdit); + formatter.setFormatters({&formatterB, &formatterA}); + + // Stress-test the implementation by providing the input in chunks, splitting at all possible + // offsets. + for (int i = 0; i < input.length(); ++i) { + formatter.appendMessage(input.left(i), NormalMessageFormat); + formatter.appendMessage(input.mid(i), NormalMessageFormat); + QCOMPARE(textEdit.toPlainText(), output); + formatter.clear(); + } +} +#endif // WITH_TESTS } // namespace Core diff --git a/src/plugins/coreplugin/outputwindow.h b/src/plugins/coreplugin/outputwindow.h index a90e9ced667..77703f0b9a3 100644 --- a/src/plugins/coreplugin/outputwindow.h +++ b/src/plugins/coreplugin/outputwindow.h @@ -56,12 +56,13 @@ public: OutputWindow(Context context, const QString &settingsKey, QWidget *parent = nullptr); ~OutputWindow() override; - void setFormatter(Utils::OutputFormatter *formatter); + void setFormatters(const QList &formatters); void appendMessage(const QString &out, Utils::OutputFormat format); void grayOutOldContent(); void clear(); + void flush(); void scrollToBottom(); diff --git a/src/plugins/projectexplorer/appoutputpane.cpp b/src/plugins/projectexplorer/appoutputpane.cpp index d9261efa5e2..cb24b7c1c24 100644 --- a/src/plugins/projectexplorer/appoutputpane.cpp +++ b/src/plugins/projectexplorer/appoutputpane.cpp @@ -154,7 +154,7 @@ AppOutputPane::RunControlTab::RunControlTab(RunControl *runControl, Core::Output runControl(runControl), window(w) { if (runControl && w) - w->setFormatter(runControl->outputFormatter()); + w->setFormatters(runControl->outputFormatters()); } AppOutputPane::AppOutputPane() : @@ -404,7 +404,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) if (tab.runControl) tab.runControl->initiateFinish(); tab.runControl = rc; - tab.window->setFormatter(rc->outputFormatter()); + tab.window->setFormatters(rc->outputFormatters()); handleOldOutput(tab.window); @@ -743,8 +743,12 @@ void AppOutputPane::slotRunControlFinished() { auto *rc = qobject_cast(sender()); QTimer::singleShot(0, this, [this, rc]() { slotRunControlFinished2(rc); }); - if (rc->outputFormatter()) - rc->outputFormatter()->flush(); + for (const RunControlTab &t : m_runControlTabs) { + if (t.runControl == rc) { + t.window->flush(); + break; + } + } } void AppOutputPane::slotRunControlFinished2(RunControl *sender) diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp index 3eeedee281d..f259d1f7669 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.cpp +++ b/src/plugins/projectexplorer/compileoutputwindow.cpp @@ -135,7 +135,7 @@ CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) : m_outputWindow->setReadOnly(true); m_outputWindow->setUndoRedoEnabled(false); m_outputWindow->setMaxCharCount(Core::Constants::DEFAULT_MAX_CHAR_COUNT); - m_outputWindow->setFormatter(m_formatter); + m_outputWindow->setFormatters({m_formatter}); // Let selected text be colored as if the text edit was editable, // otherwise the highlight for searching is too light diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp index 37bf2f3c49f..18cf5a3099f 100644 --- a/src/plugins/projectexplorer/runcontrol.cpp +++ b/src/plugins/projectexplorer/runcontrol.cpp @@ -279,7 +279,6 @@ public: : q(parent), runMode(mode) { icon = Icons::RUN_SMALL_TOOLBAR; - outputFormatter = new OutputFormatter(); } ~RunControlPrivate() override @@ -289,7 +288,7 @@ public: q = nullptr; qDeleteAll(m_workers); m_workers.clear(); - delete outputFormatter; + qDeleteAll(outputFormatters); } Q_ENUM(RunControlState) @@ -334,7 +333,7 @@ public: Kit *kit = nullptr; // Not owned. QPointer target; // Not owned. QPointer project; // Not owned. - QPointer outputFormatter = nullptr; + QList outputFormatters; std::function promptToStop; std::vector m_factories; @@ -385,10 +384,8 @@ void RunControl::setTarget(Target *target) d->buildEnvironment = bc->environment(); } - delete d->outputFormatter; - d->outputFormatter = OutputFormatterFactory::createFormatter(target); - if (!d->outputFormatter) - d->outputFormatter = new OutputFormatter(); + QTC_CHECK(d->outputFormatters.isEmpty()); + d->outputFormatters = OutputFormatterFactory::createFormatters(target); setKit(target->kit()); d->project = target->project(); @@ -831,9 +828,9 @@ void RunControlPrivate::showError(const QString &msg) q->appendMessage(msg + '\n', ErrorMessageFormat); } -Utils::OutputFormatter *RunControl::outputFormatter() const +QList RunControl::outputFormatters() const { - return d->outputFormatter; + return d->outputFormatters; } Core::Id RunControl::runMode() const @@ -1601,11 +1598,7 @@ static QList g_outputFormatterFactories; OutputFormatterFactory::OutputFormatterFactory() { - // This is a bit cheating: We know that only two formatters exist right now, - // and this here gives the second (python) implicit more priority. - // For a final solution, probably all matching formatters should be used - // in parallel, so there's no need to invent a fancy priority system here. - g_outputFormatterFactories.prepend(this); + g_outputFormatterFactories.append(this); } OutputFormatterFactory::~OutputFormatterFactory() @@ -1613,13 +1606,14 @@ OutputFormatterFactory::~OutputFormatterFactory() g_outputFormatterFactories.removeOne(this); } -OutputFormatter *OutputFormatterFactory::createFormatter(Target *target) +QList OutputFormatterFactory::createFormatters(Target *target) { + QList formatters; for (auto factory : qAsConst(g_outputFormatterFactories)) { if (auto formatter = factory->m_creator(target)) - return formatter; + formatters << formatter; } - return nullptr; + return formatters; } void OutputFormatterFactory::setFormatterCreator diff --git a/src/plugins/projectexplorer/runcontrol.h b/src/plugins/projectexplorer/runcontrol.h index cda6d6ab55e..99c6a7636cb 100644 --- a/src/plugins/projectexplorer/runcontrol.h +++ b/src/plugins/projectexplorer/runcontrol.h @@ -238,7 +238,7 @@ public: Utils::FilePath targetFilePath() const; Utils::FilePath projectFilePath() const; - Utils::OutputFormatter *outputFormatter() const; + QList outputFormatters() const; Core::Id runMode() const; const Runnable &runnable() const; @@ -309,7 +309,7 @@ protected: public: virtual ~OutputFormatterFactory(); - static Utils::OutputFormatter *createFormatter(Target *target); + static QList createFormatters(Target *target); protected: void setFormatterCreator(const std::function &creator); diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp index 5ea0bf1f0f7..a01d08e39ab 100644 --- a/src/plugins/python/pythonrunconfiguration.cpp +++ b/src/plugins/python/pythonrunconfiguration.cpp @@ -71,12 +71,16 @@ public: } private: - void doAppendMessage(const QString &text, OutputFormat format) final + Status handleMessage(const QString &text, OutputFormat format) final { if (!m_inTraceBack) { m_inTraceBack = format == StdErrFormat && text.startsWith("Traceback (most recent call last):"); - OutputFormatter::doAppendMessage(text, format); + if (m_inTraceBack) { + OutputFormatter::appendMessageDefault(text, format); + return Status::InProgress; + } + return Status::NotHandled; } const Core::Id category(PythonErrorTaskCategory); @@ -90,9 +94,10 @@ private: const auto fileName = FilePath::fromString(match.captured(3)); const int lineNumber = match.capturedRef(4).toInt(); m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category}); - return; + return Status::InProgress; } + Status status = Status::InProgress; if (text.startsWith(' ')) { // Neither traceback start, nor file, nor error message line. // Not sure if that can actually happen. @@ -111,18 +116,21 @@ private: TaskHub::addTask(*rit); m_tasks.clear(); m_inTraceBack = false; + status = Status::Done; } - OutputFormatter::doAppendMessage(text, format); + OutputFormatter::appendMessageDefault(text, format); + return status; } - void handleLink(const QString &href) final + bool handleLink(const QString &href) final { const QRegularExpressionMatch match = filePattern.match(href); if (!match.hasMatch()) - return; + return false; const QString fileName = match.captured(3); const int lineNumber = match.capturedRef(4).toInt(); Core::EditorManager::openEditorAt(fileName, lineNumber); + return true; } void reset() override diff --git a/src/plugins/qtsupport/qtoutputformatter.cpp b/src/plugins/qtsupport/qtoutputformatter.cpp index fdaef6004be..76cb10422bd 100644 --- a/src/plugins/qtsupport/qtoutputformatter.cpp +++ b/src/plugins/qtsupport/qtoutputformatter.cpp @@ -45,6 +45,8 @@ #include #include +#include + using namespace ProjectExplorer; using namespace Utils; @@ -95,14 +97,13 @@ protected: virtual void openEditor(const QString &fileName, int line, int column = -1); private: - void doAppendMessage(const QString &text, Utils::OutputFormat format) override; - void handleLink(const QString &href) override; + Status handleMessage(const QString &text, Utils::OutputFormat format) override; + bool handleLink(const QString &href) override; void updateProjectFileList(); LinkResult matchLine(const QString &line) const; - void appendMessagePart(const QString &txt, const QTextCharFormat &fmt); void appendLine(const LinkResult &lr, const QString &line, const QTextCharFormat &format); - void doAppendMessage(const QString &txt, const QTextCharFormat &format); + Status doAppendMessage(const QString &txt, const QTextCharFormat &format); QtOutputFormatterPrivate *d; friend class QtSupportPlugin; // for testing @@ -160,25 +161,39 @@ LinkResult QtOutputFormatter::matchLine(const QString &line) const return lr; } -void QtOutputFormatter::doAppendMessage(const QString &txt, const QTextCharFormat &format) +OutputFormatter::Status QtOutputFormatter::doAppendMessage(const QString &txt, + const QTextCharFormat &format) { + // FIXME: We'll do the ANSI parsing twice if there is no match. + // Ideally, we'd (optionally) pre-process ANSI escape codes in the + // base class before passing the text here, but then we can no longer + // pass complete lines... const QList ansiTextList = parseAnsi(txt, format); - for (const FormattedText &output : ansiTextList) - appendMessagePart(output.text, output.format); + QList> parts; + bool hasMatches = false; + for (const FormattedText &output : ansiTextList) { + const LinkResult lr = matchLine(output.text); + if (!lr.href.isEmpty()) + hasMatches = true; + parts << std::make_tuple(output.text, output.format, lr); + } + if (!hasMatches) + return Status::NotHandled; + for (const auto &part : parts) { + const LinkResult &lr = std::get<2>(part); + const QString &text = std::get<0>(part); + const QTextCharFormat &fmt = std::get<1>(part); + if (!lr.href.isEmpty()) + appendLine(lr, text, fmt); + else + cursor().insertText(text, fmt); + } + return Status::Done; } -void QtOutputFormatter::doAppendMessage(const QString &txt, OutputFormat format) +QtOutputFormatter::Status QtOutputFormatter::handleMessage(const QString &txt, OutputFormat format) { - doAppendMessage(txt, charFormat(format)); -} - -void QtOutputFormatter::appendMessagePart(const QString &txt, const QTextCharFormat &fmt) -{ - const LinkResult lr = matchLine(txt); - if (!lr.href.isEmpty()) - appendLine(lr, txt, fmt); - else - cursor().insertText(txt, fmt); + return doAppendMessage(txt, charFormat(format)); } void QtOutputFormatter::appendLine(const LinkResult &lr, const QString &line, @@ -189,7 +204,7 @@ void QtOutputFormatter::appendLine(const LinkResult &lr, const QString &line, cursor().insertText(line.mid(lr.end), format); } -void QtOutputFormatter::handleLink(const QString &href) +bool QtOutputFormatter::handleLink(const QString &href) { if (!href.isEmpty()) { static const QRegularExpression qmlLineColumnLink("^(" QT_QML_URL_REGEXP ")" // url @@ -205,7 +220,7 @@ void QtOutputFormatter::handleLink(const QString &href) const int line = qmlLineColumnMatch.captured(2).toInt(); const int column = qmlLineColumnMatch.captured(3).toInt(); openEditor(getFileToOpen(fileUrl), line, column - 1); - return; + return true; } static const QRegularExpression qmlLineLink("^(" QT_QML_URL_REGEXP ")" // url @@ -220,7 +235,7 @@ void QtOutputFormatter::handleLink(const QString &href) fileUrl = QUrl::fromLocalFile(filePath.mid(int(strlen(scheme)))); const int line = qmlLineMatch.captured(2).toInt(); openEditor(getFileToOpen(fileUrl), line); - return; + return true; } QString fileName; @@ -250,9 +265,10 @@ void QtOutputFormatter::handleLink(const QString &href) if (!fileName.isEmpty()) { fileName = getFileToOpen(QUrl::fromLocalFile(fileName)); openEditor(fileName, line); - return; + return true; } } + return false; } void QtOutputFormatter::openEditor(const QString &fileName, int line, int column) @@ -504,8 +520,13 @@ void QtSupportPlugin::testQtOutputFormatter_appendMessage() QFETCH(QString, outputText); QFETCH(QTextCharFormat, inputFormat); QFETCH(QTextCharFormat, outputFormat); + if (outputFormat == QTextCharFormat()) + outputFormat = formatter.charFormat(DebugFormat); + if (inputFormat != QTextCharFormat()) + formatter.overrideTextCharFormat(inputFormat); - formatter.doAppendMessage(inputText, inputFormat); + formatter.appendMessage(inputText, DebugFormat); + formatter.flush(); QCOMPARE(edit.toPlainText(), outputText); QCOMPARE(edit.currentCharFormat(), outputFormat); diff --git a/src/plugins/serialterminal/serialoutputpane.cpp b/src/plugins/serialterminal/serialoutputpane.cpp index e9dfb50ce7c..eab36c46be7 100644 --- a/src/plugins/serialterminal/serialoutputpane.cpp +++ b/src/plugins/serialterminal/serialoutputpane.cpp @@ -315,7 +315,7 @@ void SerialOutputPane::createNewOutputWindow(SerialControl *rc) this, fontSettingsChanged); fontSettingsChanged(); ow->setWindowTitle(tr("Serial Terminal Window")); - ow->setFormatter(formatter); + ow->setFormatters({formatter}); // TODO: wordwrap, maxLineCount, zoom/wheelZoom (add to settings) auto controlTab = SerialControlTab(rc, ow); diff --git a/src/plugins/vcsbase/vcsoutputformatter.cpp b/src/plugins/vcsbase/vcsoutputformatter.cpp index 4915dda4038..4145bd0618a 100644 --- a/src/plugins/vcsbase/vcsoutputformatter.cpp +++ b/src/plugins/vcsbase/vcsoutputformatter.cpp @@ -42,14 +42,17 @@ VcsOutputFormatter::VcsOutputFormatter() : { } -void VcsOutputFormatter::doAppendMessage(const QString &text, Utils::OutputFormat format) +VcsOutputFormatter::Status VcsOutputFormatter::handleMessage(const QString &text, + Utils::OutputFormat format) { QRegularExpressionMatchIterator it = m_regexp.globalMatch(text); + if (!it.hasNext()) + return Status::NotHandled; int begin = 0; while (it.hasNext()) { const QRegularExpressionMatch match = it.next(); const QTextCharFormat normalFormat = charFormat(format); - OutputFormatter::doAppendMessage(text.mid(begin, match.capturedStart() - begin), format); + appendMessageDefault(text.mid(begin, match.capturedStart() - begin), format); QTextCursor tc = plainTextEdit()->textCursor(); QStringView url = match.capturedView(); begin = match.capturedEnd(); @@ -61,15 +64,17 @@ void VcsOutputFormatter::doAppendMessage(const QString &text, Utils::OutputForma tc.insertText(url.toString(), linkFormat(normalFormat, url.toString())); tc.movePosition(QTextCursor::End); } - OutputFormatter::doAppendMessage(text.mid(begin), format); + appendMessageDefault(text.mid(begin), format); + return Status::Done; } -void VcsOutputFormatter::handleLink(const QString &href) +bool VcsOutputFormatter::handleLink(const QString &href) { if (href.startsWith("http://") || href.startsWith("https://")) QDesktopServices::openUrl(QUrl(href)); else if (!href.isEmpty()) emit referenceClicked(href); + return true; } void VcsOutputFormatter::fillLinkContextMenu( diff --git a/src/plugins/vcsbase/vcsoutputformatter.h b/src/plugins/vcsbase/vcsoutputformatter.h index fe1bae89fdf..5074fc33923 100644 --- a/src/plugins/vcsbase/vcsoutputformatter.h +++ b/src/plugins/vcsbase/vcsoutputformatter.h @@ -37,14 +37,15 @@ class VcsOutputFormatter : public Utils::OutputFormatter public: VcsOutputFormatter(); ~VcsOutputFormatter() override = default; - void doAppendMessage(const QString &text, Utils::OutputFormat format) override; - void handleLink(const QString &href) override; + bool handleLink(const QString &href) override; void fillLinkContextMenu(QMenu *menu, const QString &workingDirectory, const QString &href); signals: void referenceClicked(const QString &reference); private: + Status handleMessage(const QString &text, Utils::OutputFormat format) override; + const QRegularExpression m_regexp; }; diff --git a/src/plugins/vcsbase/vcsoutputwindow.cpp b/src/plugins/vcsbase/vcsoutputwindow.cpp index 2391a1bb03c..f1ca4da8681 100644 --- a/src/plugins/vcsbase/vcsoutputwindow.cpp +++ b/src/plugins/vcsbase/vcsoutputwindow.cpp @@ -123,7 +123,7 @@ OutputWindowPlainTextEdit::OutputWindowPlainTextEdit(QWidget *parent) : setFrameStyle(QFrame::NoFrame); m_formatter = new VcsOutputFormatter; m_formatter->setBoldFontEnabled(false); - setFormatter(m_formatter); + setFormatters({m_formatter}); auto agg = new Aggregation::Aggregate; agg->add(this); agg->add(new Core::BaseTextFind(this));